Live Demo Github
簡介 首頁由 5 個部分組成:Header 首頁大圖、Jumbotron 資訊塊、Accordion 常見問題、Otp form 訂閱表單、Footer 頁尾。每一塊都為單獨的component,組合起來後才渲染到瀏覽器。每一個 component 的創建邏輯相似:創建需要的元件 > 創建組裝元件的 container,把需要的元件排進去 > 添加樣式。
分開創建 component 除了便於維護外,最大的功能在於可以重複使用,就如 part 4 的 Opt form 在 part 5 的 header 中可以直接套用。
Part 1 : Jumbotron 資訊塊 + Global style
在首頁裡一共有 3 個 Jumbotron,其組成有:照片、標題、副標題。會先創需要的元件,再將他們放進 container 裡,最後進行樣式調整。
樣式部分不會全部都放在筆記裡,主要記下架構邏輯的部分。
創建基本檔案
components > jumbotron > index.js : 處理 Jumbotron
components > jumbotron > styles > jumbotron.js : 引入 styled ,在裡面處理 styled component
Home.js : 首頁大圖會出現的地方
containers > jumbotron.js
index.js 中創立 jumbotron 需要的所有元件:container, title, subtitle, Image 和後來添加的 pane.
index.js (Component > Jumbotron) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React from "react" import { Item, Inner, Container, Title, Subtitle } from "./styles/Jumbotron" function Jumbotron ({ children, direction = "row" , ...restProps } ) { return ( <Item {...restProps}> <Inner direction={direction}>{children}</Inner> </Item> ) } Jumbotron.Container = function JumbotronContainer ({ children, ...restProps } ) { return <Container {...restProps }> {children}</Container > } Jumbotron.Title = function JumbotronTitle ({ children, ...restProps } ) { return <Title {...restProps }> {children}</Title > } Jumbotron.SubTitle = function JumbotronSubTitle ({ children, ...restProps } ) { return <Subtitle {...restProps }> {children}</Subtitle > } Jumbotron.Pane = function JumbotronPane ({ children, ...restProps } ) { return <Pane {...restProps }> {children}</Pane > } Jumbotron.Image = function JumbotronSubTitle ({ ...restProps } ) { return <Image {...restProps }/> } export default Jumbotron
第 4 行 : 引入 Jumbotron.js 中設定的每個區塊
第 6 行 : 創建一個會傳入 children , direction (jumbo.json中有出現的) 以及剩餘的 props 參數的 function.
第 7 -12 行 : 這個 function 會返回一個 < Item>, 他的 children 是 < Inner>, < Inner > 的 children 可以是下面的 container/ title/ subtitle
第 14 行 : 創造新的變數,這個變數是一個 function,傳入的參數為 children 以及剩餘的 props. 後返回一個 < Container > element.
第 15 行 : < Container > element 傳入的參數說所有的 props, children 為 children
應該會是字串,顯示在熒幕上的字等等的
比如說 Home.js 中的 < Jumbotron.Title > 包的東西就是 < Title > 的children
Home.js (Pages) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 import React from 'react' import Jumbotron from '../components/jumbotron' export default function Home ( ) { return ( <Jumbotron.Container> <Jumbotron.Title>Hello</Jumbotron.Title> <Jumbotron.SubTitle>Huala</Jumbotron.SubTitle> </Jumbotron.Container> ) }
第 4 行 : 引入上面建立好的 Jumbotron
第 8 行 : 可以直接使用 index.js 中創造好的變數
把首頁需要的資訊渲染到畫面上
把在 Home.js 中的 container component 移到另一個檔案 : Jumbotron.js ( container > Jumbotron.js),再引入 Home.js 中
jumbotron.js (Containers > jumbotron.js) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import React from "react" import Jumbotron from "../components/jumbotron" import jumboData from "../fixtures/jumbo.json" function JumbotronContainer ( ) { return ( <Jumbotron.Container> {jumboData.map((item ) => ( <Jumbotron key={item.id} direction={item.direction}> <Jumbotron.Pane> <Jumbotron.Title >{item.title}</Jumbotron.Title> <Jumbotron.SubTitle>{item.subTitle}</Jumbotron.SubTitle> </Jumbotron.Pane> <Jumbotron.Pane> <Jumbotron.Image src={item.image} alt={item.name}></Jumbotron.Image> </Jumbotron.Pane> </Jumbotron> ))} </Jumbotron.Container> ) } export { JumbotronContainer }
jumbo.json ( fixtures > jumbo.json ) >folded 1 2 3 4 5 6 7 8 9 10 11 { "id" : 1 , "title" : "Enjoy on your TV." , "subTitle" : "Watch on smart TVs, PlayStation, Xbox, Chromecast, Apple TV, Blu-ray players and more." , "image" : "/images/misc/home-tv.png" , "alt" : "Tiger King on Netflix" , "direction" : "row" }
設計首頁每一個 Jumbotron
Jumbotron.js (Component > Jumbotron > styles) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import styled from 'styled-components' export const Inner = styled.div` display: flex; align-items: center; justify-content: space-between; flex-direction: ${({ direction }) => direction} ; //根據jumbo.json 裡的 direction 改變 max-width: 1100px; margin: auto; width: 100%; @media(max-width: 1000px) { flex-direction: column; } ` export const Pane = styled.div` width : 50%; @media (max-width: 1000px) { width:100%; padding: 0 45px; text-alighn: center; } ` export const Item = styled.div` display: flex; border-bottom: 8px solid #222; padding: 50px 5%; color: white; overflow: hidden; ` export const Container = styled.section` background-color: black; @media (max-width: 1000px) { ${Item} :last-of-type h2 { margin-bottom: 50px; } text-align: center; } ` export const Title = styled.h1` font-size: 50px; line-height: 1.1; margin-bottom: 8px; @media (max-width: 600px) { font-size: 35px; } ` export const SubTitle = styled.h2` font-size: 26px; font-weight: normal; line-height: normal; @media (max-width: 600px) { font-size: 18px; } ` export const Image = styled.img` max-width: 100%; height: auto; `
整理 Component Library & Implementing Global Styles With Styled Components
Component Library
之後會有很多 component 要加進來,如果每次都單獨 import 檔案進來看起來就會冗冗的
在 index.js (Component) 把路徑設成 {Jumbotron} 之後要引用這個路徑就可以直接寫 Jumbotron
更改 jumbotron.js (containers) 的路徑
index.js (Component) >folded 1 export { default as Jumbotron } from './jumbotron' ;
jumbotron.js (containers) >folded 1 2 3 4 import { Jumbotron } from "../components"
Global Styles
使用 styled component 會難以估計每個元件預設的值是多少
可以創建一個新的 conponent 來儲存 global 的樣式
這個 component 要加在最根部,進行渲染的那一頁 (index.js)
filename : global-style.js >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { createGlobalStyle } from 'styled-components' ;export const GlobalStyles = createGlobalStyle` html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: #000000; color: #333333; font-size: 16px; margin: 0; } ` ;
index.js >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import React from "react" import ReactDOM from "react-dom" import { BrowserRouter } from "react-router-dom" import { GlobalStyles } from "./global-style" import "./index.css" import App from "./App" ReactDOM.render( <> <GlobalStyles/> <BrowserRouter> <App /> </BrowserRouter> </>, document .getElementById('root' ) );
Footer 一共有 4 欄,其排版會使用 gird 來進行。在縮小的時候會先變成 3 欄 ,再變為兩欄。
創建基本檔案
components > footer > index.js : footer 會在裡面處理
components > footer > styles > footer.js : 引入 styled ,在裡面處理 styled component
containers > footer.js
在 index.js (components 的) 添加 export { default as Footer } from './footer';
, 會在 footer.js (containers) 中引入
Home.js 中增加 footer container
建立需要的元件 index.js 中創立 footer 需要的所有元件:container, row, column, title, link 和 break.
index.js (conponents > footer) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import React from "react" import { Container, Row, Column, Link, Title, Break, Text } from "./styles/footer" function Footer ({ children, ...restProps } ) { return <Container {...restProps }> {children}</Container > } Footer.Row = function FooterRow ({ children, ...restProps } ) { return <Row {...restProps }> {children}</Row > } Footer.Column = function FooterColumn ({ children, ...restProps } ) { return <Column {...restProps }> {children}</Column > } Footer.Link = function FooterLink ({ children, ...restProps } ) { return <Link {...restProps }> {children}</Link > } Footer.Title = function FooterTitle ({ children, ...restProps } ) { return <Title {...restProps }> {children}</Title > } Footer.Text = function FooterText ({ children, ...restProps } ) { return <Text {...restProps }> {children}</Text > } Footer.Break = function FooterBreak ({ ...restProps } ) { return <Break {...restProps }/> } export { FooterContainer }
第 4 行 : 引入 styled component
第 6 行 : 設置 Footer container, children 是下面的一大串 (row, column…)
第 10 行 : 設置 Row function ,會傳入 children 和其他 props, return < Row >,其他的同理
第 30 行 : 排版的時候會用到空行,所以這裡設置 break component,排版的時候就可以用
把每一個需要的鏈接都放在 container 裡. 因為使用 grid 所以需要 Column 和 Row.
footer.js (containers > footer.js) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import React from "react" import { Footer } from "../components" function FooterContainer ( ) { return ( <Footer> <Footer.Title>Question?Contact us.</Footer.Title> <Footer.Break /> <Footer.Row> <Footer.Column> <Footer.Link href="#" >FAQs</Footer.Link> <Footer.Link href="#" >Investor Relations</Footer.Link> <Footer.Link href="#" >Ways to watch</Footer.Link> <Footer.Link href="#" >Corparate Informations</Footer.Link> <Footer.Link href="#" >Netflix Originals</Footer.Link> </Footer.Column> · · · </Footer.Column> </Footer.Row> <Footer.Break/> </Footer> ) } export default FooterContainer
Part 3: Accordion 常見問題
常見問題的部分會使用手風琴式選單來製作(可以點開收起)。其中需要的元件有標題、問題、內容(回答)、點擊按鈕。
常見問題 (FAQ) 與 Jumbotron 製作的方式類似,都有幾筆類型相同、格式也相同的資料,因此渲染到畫面上的方法是一樣的。
創建基本檔案
components > Accordion > index.js : Accordion 會在裡面處理
components > Accordion > styles > accordion .js : 引入 styled ,在裡面處理 styled component
containers > faq.js
在 index.js (components 的) 添加 export { default as Accordion } from './accordion';
, 會在 accordion .js (containers) 中引入
Home.js 中增加 Accordion container
建立需要的元件 + 把需要的資料渲染到瀏覽器上 (直到這裡的步驟都與 jumbotron 的相同)
建立 index.js (之後會詳細處理 state 的部分,這裡不放程式碼) ,創建基本的架構
建立 accordion.js (component > accrodion > styles),確保index.js 中需要的元件都有在 accordion.js 中出現,避免報錯
FAQ 資料保存在 faqs.json (fixtures) 中,一共有4筆。資料形態如下:
faqs.json >folded 1 2 3 4 5 6 7 { "id" : 1 , "header" : "What is Netflix?" , "body" : "Netflix is a streaming service that offers a wide variety of award-winning TV programmes, films, anime, documentaries and more – on thousands of internet-connected devices.\n\nYou can watch as much as you want, whenever you want, without a single advert – all for one low monthly price. There's always something new to discover, and new TV programmes and films are added every week!" },
faqs.js (containers) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import React from "react" import { Accordion } from "../components" import faqsData from "../fixtures/faqs.json" function FaqsContainer ( ) { return ( <Accordion > <Accordion.Title>Frequently Asked Questions</Accordion.Title> <Accordion.Frame> {faqsData.map((item ) => ( <Accordion.Item key={item.id}> <Accordion.Header>{item.header}</Accordion.Header> <Accordion.Body>{item.body}</Accordion.Body> </Accordion.Item> ))} </Accordion.Frame> </Accordion> ) } export { FaqsContainer}
提供用戶訂閱的 Opt Form 會放在 FAQs 那一個部分裡,因此只需要創建 Opt Form 的 component,不需要將其 container 獨立出來。
另外文末也會進行 Router 處理。
創建基本檔案
components > Opt-Form > index.js : opt-form 會在裡面處理
components > Opt-Form > styles > opt-form .js : 引入 styled ,在裡面處理 styled component
在 index.js (components 的) 添加 export { default as Accordion } from ‘./Opt-Form’;
index.js (components > opt-form) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import React from 'react' ;import { Container, Input, Break , Button, Text } from './styles/opt-form' ;function OptForm ({ children, ...restProps } ) { return <Container {...restProps }> {children}</Container > } OptForm.Input = function OptFormInput ({ ...restProps } ) { return <Input {...restProps } /> } OptForm.Button = function OptFormButton ({ children, ...restProps } ) { return ( <Button {...restProps}> {children} <i className="fas fa-chevron-right" > </i> </Button> ) } OptForm.Break = function OptBreak ({ ...restProps } ) { return <Break {...restProps } /> ; } OptForm.Text = function OptFormText ({ children, ...restProps } ) { return <Text {...restProps }> {children}</Text > } export default OptForm
處理 components (pages) 的 router
如果之後要更換路徑,只要更新一個地方就可以了,比較容易維護
創建好之後就可以更換 App.js 中的路徑
Routes.js (constants) >folded 1 2 3 4 5 6 export const HOME = '/' export const BROWSE = '/browse' export const SIGN_UP = '/signup' export const SIGN_IN = '/signin'
App.js >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React from 'react' import { Switch, Route } from 'react-router-dom' import * as ROUTES from './constants/routes' ; import Home from "./pages/Home" function App ( ) { return ( <Switch> <Route path={ROUTES.SIGN_IN}> <p>Sign in page</p> </Route> <Route path={ROUTES.SIGN_UP}> <p>Sign up page</p> </Route> <Route path={ROUTES.BROWSE}> <p>browse page</p> </Route> <Route path={ROUTES.HOME}> <Home /> </Route> </Switch> ); } export default App;
創建基本檔案
components > header > index.js : header 會在裡面處理
components > header > styles > header .js : 引入 styled ,在裡面處理 styled component
containers > header.js
在 index.js (components 的) 添加 export { default as Header } from ‘./header’; , 會在 header .js (containers) 中引入
Home.js 中增加 header container
建構需要的元件 頁首需要的元件:背景, Logo, Sign In button, opt form . Logo 和 Sign In button 點擊後會跳往指定的頁。Opt form 上一個部分已經做了,所以可以直接使用。
index.js (containers > header) >folded 1 2 3 4 5 6 7 <p> <p>import React from “react”<br>import { Link as ReachRouterLink } from “react-router-dom”<br>import { Background, Container, Logo, ButtonLink } from “./styles/header”</p> <p>function Header ({ bg= true , children, …restProps } ) {<br> return bg ? <Background {…restProps}>{children}</Background> : children<br>}</ p> <p>Header.Frame = function HeaderFrame ({ children, …restProps } ) {<br> return <Container {…restProps}>{children}</Container><br>}</ p> <p>Header.Logo = function HeaderLogo ({ to, …restProps } ) {<br> return (<br> <ReachRouterLink to={to}><br> <Logo {…restProps} /><br> </ReachRouterLink><br> )<br>}</p> <p>Header.ButtonLink = function HeaderButtonLink({ children, …restProps }){<br> return <ButtonLink {…restProps}>{children}</ButtonLink><br>}</p> <p>export default Header</p>
第 4 行 : logo & signin button 需要使用 Link
第 7 行 : bg 為 true ,會顯示背景圖片
第 11 行 : Header.Frame 會返回 Container
第 15 行 : 使用 Link 把 Logo 包起來,讓他變成點擊後會跳轉的
第 23 行 : Header.ButtonLink 是 for sign in button 的
把需要的元件排進 container 裡 header.js (components > header) >folded 1 2 3 4 <p> <p>import React from “react”<br>import { Header } from “../components”<br>import * as ROUTES from “../constants/routes”;</p> <p>function HeaderContainer ({ children } ) {<br> return (<br> <Header><br> <Header.Frame><br> <Header.Logo<br> to={ROUTES.HOME}<br> src=”/images/misc/logo.png”<br> alt=”Netflix”<br> /><br> <Header.ButtonLink to={ROUTES.SIGN_IN}>Sign In</Header.ButtonLink><br> </Header.Frame><br> {children}<br> </Header><br> )<br>}</p> <p>export default HeaderContainer</p>
第 5 行 : 引入 route.js,為 logo & sign in button 加上 Link
第 11 行 : Header.Logo 是個 Link 所以需要 to
第 16 行 : 與第 11 行 同理
會有 title, subtitle, form. 為了方便,title, subtitle 會另外創一個 container 來裝。
跟上面的步驟一樣,在 containers 中創建 feature. feature 中創建 index.js 以及 styles, styles 中創建 feature.js.
index.js (components > feature) >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React from "react" import { Container, Title, SubTitle } from "./styles/feature" function Feature ({ children, ...restProps } ) { return <Container {...restProps }> {children}</Container > } Feature.Title = function FeatureTitle ({ children, ...restProps } ) { return <Title {...restProps }> {children}</Title > } Feature.SubTitle = function FeatureSubTitle ({ children, ...restProps } ) { return <SubTitle {...restProps }> {children}</SubTitle > } export default Feature
Home.js >folded 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React from 'react' import { Feature, OptForm } from "../components" import { FaqsContainer } from "../containers/faqs" import { JumbotronContainer } from "../containers/jumbotron" import { FooterContainer } from "../containers/footer" import { HeaderContainer } from "../containers/header" export default function Home ( ) { return ( <> <HeaderContainer> <Feature> <Feature.Title>Unlimited films, TV programmes and more.</Feature.Title> <Feature.SubTitle>Watch anywhere. Cancel at any time.</Feature.SubTitle> <OptForm> <OptForm.Input placeholder="Email Address" /> <OptForm.Button>Try it now</OptForm.Button> <OptForm.Break /> <OptForm.Text>Ready to watch? Enter your email to create or restart your membership.</OptForm.Text> </OptForm> </Feature> </HeaderContainer> <JumbotronContainer /> <FaqsContainer /> <FooterContainer /> </> ) }
第 4 行 : 把 feature component 傳進來
第 14 行 : 把中間整塊視為 feature, 所以用 feature component 把整塊包起來
第 15,16 行 : 傳進 title 和 subtitle
第 17 行 : 把上一個 part 做好的 Opt Form 整個傳進來
導覽頁 Netflix Clone : 主頁 Netflix Clone : 首頁 Netflix Clone : 用戶登入頁 Netflix Clone : Browser 頁 Netflix Clone : 最後整理