React 基礎


Posted by saffran on 2021-02-25

環境建置:create-react-app

環境建置分兩種:

  1. 自己從頭開始做(webpack + babel)
  2. 用現成的

接下來,會用現成的 create-react-app (這是 react 官方提供的)來做環境建置

安裝完後,可以用 npx create-react-app -V 來查詢版本

<React.StrictMode> 會幫我檢查是否有用到不該用的東西,但是因為它需要偵測,會造成 console.log 出來的結果跟預期中的不一樣,所以建議是把 <React.StrictMode> 先移除

初探 React 中的 style

方式一:inline style(這是 create-react-app 內建的方式)

style={} 大括號裡面要傳入一個物件

const titleStyle = {
  color: 'red',
  textAlign: 'center'
}

function Title({ size }) {
  if (size === 'XL') {
    return <h1>Hello</h1>
  }
  return (
    <h2 style={titleStyle}>Hello</h2>
  )
}

比較常見的寫法是把物件直接寫在 style={} 裡面:

function Title({ size }) {
  if (size === 'XL') {
    return <h1>Hello</h1>
  }
  return (
    <h2 style={{
      color: 'red',
      textAlign: 'center'
    }}>Hello</h2>
  )
}

方式二:用 className 和 css(這是 create-react-app 內建的方式)

在 JSX 語法裡面,要寫 className,不能寫 class,因為 class 是 JS 的保留字

因此,用 JSX 寫:

<div className="App"></div>

render 到畫面上時,就會是:

<div class="App"></div>

接下來,因為有用 webpack 的關係,我可以直接在 App.js 裡面 import App.css

import './App.css';

在 App.css 裡面,我就可以寫 css 的內容了

方式三:用 styled components 套件(推薦使用!)

styled components 是用 tagged template literal 建立出一套寫 css 的方法

參考文章 JavaScript ES6 中的模版字符串(template literals)和標籤模版(tagged template)

styled components 直接支援 scss, sass 的寫法
styled components 也會自動幫我加上各種 broswer 的 prefix,所以不用擔心兼容性的問題

首先,要先把套件安裝起來

安裝好後,就在 App.js 上方引入 styled components:

import styled from 'styled-components';

接著,就可以開始幫 component 加上 style 了

範例一:幫 <Description> 這個 component 加上 style

Description 就是一個「擁有這些 style 的一個 <p> component」

const Description = styled.p`
  color: salmon;
  font-weight: bold;
  border: 1px solid green;
`

function App() {
  return (
    <div className="App">
      <Title size="M" />
      <Description>
        I love you!
      </Description>
    </div>
  );
}

render 之後就會看到:
styled components 幫我做的事情就是:自動產生一個像亂碼的 className 放到元素上,並且把我寫的 css 套用上去

範例二:

const TitleWrapper = styled.h2`
  color: teal;
  &:hover {
    color: orange;
  }
  span {
    color: purple;
  }
`

function Title({ size }) {
  return (
    <TitleWrapper>Hello<span>yoyoyo</span></TitleWrapper>
  )
}

範例三:

const TodoItemWrapper = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px;
  border: 1px solid black;
`

const TodoContent = styled.div`
  color: salmon;
  font-size: ${props => props.size === 'XL' ? '20px' : '12px'}
`

const TodoButtonWrapper = styled.div``

const Button = styled.button`
  padding: 5px;
  color: teal;
  &:hover {
    color: red;
  }
  & + & {
    margin-left: 10px;
  }
`

function App() {
  return (
    <div className="App">
      <TodoItemWrapper>
        <TodoContent size="XL">I am a todo</TodoContent>
        <TodoButtonWrapper>
          <Button>已完成</Button>
          <Button>刪除</Button>
        </TodoButtonWrapper>
      </TodoItemWrapper>
    </div>
  );
}

styled components 傳 props 參數的方式

props 就像是自定義的「HTML 元素的屬性」

範例一:

<TodoContent> 傳入 size="XL" 這個 prop
在 styled component 就可以用 ${props => props.size ...} 來拿到 size="XL" 這個 prop

const TodoContent = styled.div`
  color: salmon;
  font-size: ${props => props.size === 'XL' ? '20px' : '12px'}
`

function App() {
  return (
    <div className="App">
      <TodoItemWrapper>
        <TodoContent size="XL">I am a todo</TodoContent>
        <TodoButtonWrapper>
          <Button>已完成</Button>
          <Button>刪除</Button>
        </TodoButtonWrapper>
      </TodoItemWrapper>
    </div>
  );
}

範例二:利用「短路運算子」

<TodoContent>font-size 預設是 12px
如果 <TodoContent>size="XL" 的話,就會用 font-size: 20px 覆蓋掉原本的 font-size: 12px

const TodoContent = styled.div`
  color: salmon;
  font-size: 12px;
  ${props => props.size === 'XL' && `
    font-size: 20px;
  `}
`

function App() {
  return (
    <div className="App">
      <TodoItemWrapper>
        <TodoContent size="XL">I am a todo</TodoContent>
        <TodoButtonWrapper>
          <Button>已完成</Button>
          <Button>刪除</Button>
        </TodoButtonWrapper>
      </TodoItemWrapper>
    </div>
  );
}

範例三:

<Nav> 加上 $active 這個屬性
在 styled component 就可以做出:如果 $active 是 true,才會有 background-color

const Nav = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100px;
  cursor: pointer;
  // 如果 $active 是 true,才會有 background-color
  ${(props) =>
    props.$active &&
    `
    background-color: rgba(202, 68, 111, 0.1);
  `}
`;

export default function Header() {
  return (
    <HeaderContainer>
      <LeftContainer>
        <Brand>我的第一個部落格</Brand>
        <NavbarList>
          <Nav $active>首頁</Nav> {/* 加上 $active 這個屬性 */}
          <Nav>發布文章</Nav>
        </NavbarList>
      </LeftContainer>
      <NavbarList>
        <Nav>登入</Nav>
      </NavbarList>
    </HeaderContainer>
  );
}

改寫為:用 react 的 component,裡面再接 styled components

const TodoItemWrapper = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px;
  border: 1px solid black;
`

const TodoContent = styled.div`
  color: salmon;
  font-size: 12px;
  ${props => props.size === 'XL' && `
    font-size: 20px;
  `}
`

const TodoButtonWrapper = styled.div``

const Button = styled.button`
  padding: 5px;
  color: teal;
  &:hover {
    color: red;
  }
  & + & {
    margin-left: 10px;
  }
`

function TodoItem({ size, content }){
  return (
    <TodoItemWrapper>
      <TodoContent size={size}>{content}</TodoContent>
      <TodoButtonWrapper>
        <Button>已完成</Button>
        <Button>刪除</Button>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  )
}

function App() {
  return (
    <div className="App">
      <TodoItem content={123} />
      <TodoItem content={456} size='XL' />
    </div>
  );
}

對 styled component 做 restyle

Button 做 restyle 變成 BlueButton

const Button = styled.button`
  padding: 5px;
  color: teal;
  &:hover {
    color: red;
  }
  & + & {
    margin-left: 10px;
  }
`

const BlueButton = styled(Button)`
  color: blue;
`

function TodoItem({ size, content }){
  return (
    <TodoItemWrapper>
      <TodoContent size={size}>{content}</TodoContent>
      <TodoButtonWrapper>
        <BlueButton>已完成</BlueButton>
        <Button>刪除</Button>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  )
}

對 react component 做 restyle:要接收一個 className 參數

因為 style components 是透過「傳 className」,所以要給 react component 一個 className 的參數,並把參數放在我想要 restyle 的元素上

function TodoItem({ className, size, content }){
  return (
    <TodoItemWrapper className={className}>
      <TodoContent size={size}>{content}</TodoContent>
      <TodoButtonWrapper>
        <BlueButton>已完成</BlueButton>
        <Button>刪除</Button>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  )
}

const YellowTodoItem = styled(TodoItem)`
  background-color: yellow;
`

function App() {
  return (
    <div className="App">
      <TodoItem content={123} />
      <YellowTodoItem content={456} size='XL' />
    </div>
  );
}

style components 會自動幫 background-color: yellow 產生一個新的 className,並且把這個 className 加在 <TodoItemWrapper> 上面

media query

就跟一般在寫 css 時的 media query 用法是一樣的:

const Button = styled.button`
  padding: 5px;
  color: teal;
  font-size: 20px;
  @media (min-width: 768px) {
    font-size: 16px;
  }

  &:hover {
    color: red;
  }
  & + & {
    margin-left: 10px;
  }
`

把 media query 的斷點寫在另一個檔案

在實務上,都會建立一個 constants 資料夾,在裡面新建一個檔案叫做 style.js

然後,就可以把 media query 的斷點都寫在 style.js 裡面做 export:

export const MEDIA_QUERY_MD = `@media (min-width: 768px)`;
export const MEDIA_QUERY_LG = `@media (min-width: 1000px)`;

在 App.js 要用時引入即可,這樣程式碼會比較乾淨

import { MEDIA_QUERY_MD, MEDIA_QUERY_LG} from './constants/style';

const Button = styled.button`
  padding: 5px;
  color: teal;
  font-size: 20px;
  ${MEDIA_QUERY_MD} {
    font-size: 16px;
  }

  ${MEDIA_QUERY_LG} {
    font-size: 12px;
  }

  &:hover {
    color: red;
  }
  & + & {
    margin-left: 10px;
  }
`

在 styled components 使用變數

使用變數的做法是:傳一個 global 的參數,這樣在其他檔案就可以直接使用

首先,要在最上層的檔案 index.js 引入 ThemeProvider

<ThemeProvider></ThemeProvider> 是一個 component
然後,用 <ThemeProvider></ThemeProvider> 把我要 render 的 <App /> 包住

<ThemeProvider></ThemeProvider> 要接收一個 theme 參數,在 theme 裡面就可以自己定義我想要的變數,例如 colors

import { ThemeProvider } from 'styled-components';

const theme = {
  colors: {
    primary_300: '#ef9898',
    primary_400: '#ec6b6b',
    primary_500: '#ea1212',
  }
}

ReactDOM.render(
  <ThemeProvider theme={theme}>
    <App />
  </ThemeProvider>
  ,
  document.getElementById('root')
);

接著,在 App.js 我就可以用 props.theme.colors.primary_500 來拿到 primary_500: '#ea1212' 這個顏色

const TodoContent = styled.div`
  color: ${props => props.theme.colors.primary_500};
  font-size: 12px;
  ${props => props.size === 'XL' && `
    font-size: 20px;
  `}
`

把 component 獨立成不同的檔案

當 component 越來越多時,就可以把不同的 component 獨立成不同的檔案

例如

新建一個檔案叫做 TodoItem.js,裡面就只放 TodoItem 這個 component,然後把它 export

通常都會用 export default 來 export component

import styled from 'styled-components';
import { MEDIA_QUERY_MD, MEDIA_QUERY_LG} from './constants/style';

const TodoItemWrapper = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 16px;
  border: 1px solid black;
  margin-bottom: 10px;
`

const TodoContent = styled.div`
  color: ${props => props.theme.colors.primary_500};
  font-size: 12px;
  ${props => props.size === 'XL' && `
    font-size: 20px;
  `}
`

const TodoButtonWrapper = styled.div``

const Button = styled.button`
  padding: 5px;
  color: teal;
  font-size: 20px;
  ${MEDIA_QUERY_MD} {
    font-size: 16px;
  }

  ${MEDIA_QUERY_LG} {
    font-size: 12px;
  }

  &:hover {
    color: red;
  }
  & + & {
    margin-left: 10px;
  }
`

const BlueButton = styled(Button)`
  color: blue;
`

export default function TodoItem({ className, size, content }){
  return (
    <TodoItemWrapper className={className}>
      <TodoContent size={size}>{content}</TodoContent>
      <TodoButtonWrapper>
        <BlueButton>已完成</BlueButton>
        <Button>刪除</Button>
      </TodoButtonWrapper>
    </TodoItemWrapper>
  )
}

在 App.js 就可以引入 TodoItem 拿來用

import styled from 'styled-components';
import TodoItem from './TodoItem';

const YellowTodoItem = styled(TodoItem)`
  background-color: yellow;
`

function App() {
  return (
    <div className="App">
      <TodoItem content={123} />
      <YellowTodoItem content={456} size='XL' />
    </div>
  );
}

export default App;

一探 JSX 背後的秘密

當我們在寫 JSX 的語法時,背後是透過 Babel 幫我們轉成 JavaScript 的程式碼
JSX:

<Button size="XL">hello</Button>

轉成 JavaScript 的程式碼:

React.createElement(Button, {
  size: "XL"
}, "hello");

初探 state

在 React 裡面,如果要用 state 的話,要在 App.js 寫這行:

import React from 'react';

從 React 16.8 之後,出現了 hooks 這個新的用法

import styled from 'styled-components';
import TodoItem from './TodoItem';
import React from 'react';

const YellowTodoItem = styled(TodoItem)`
  background-color: yellow;
`

function App() {
  const [counter, setCounter] = React.useState(0);
  const handleButtonClick = () => {
    setCounter(counter + 1)
  }
  return (
    <div className="App">
      counter: {counter}
      <button onClick={handleButtonClick}>increment</button>
      <TodoItem content={123} />
      <YellowTodoItem content={456} size='XL' />
    </div>
  );
}

export default App;

React.useState() 是一個 hook,透過這個 hook 讓我們可以在 function component 裡面使用一個 state

React.useState() 會回傳一個陣列:

  • 陣列裡面的第一個值是「state 的值」
    • state 的初始值要寫在 React.useState() 小括號裡面
  • 陣列裡面的第二個值 setCounter 是一個 function,呼叫 setCounter 這個 function 來更新 state

const [counter, setCounter] = React.useState(0) 是「解構」的寫法

可以看成是這樣:
用「解構」拿出來的 a 就會是 123,b 就會是 456

function useState() {
  return [123, 456]
}

const [a, b] = useState()

output:
點擊 button,counter 就會 +1

React 的這個流程是:
打開網頁後會做第一次的 render,render 就是指:React 會去執行 App() 這個 function,把 App() return 的東西放到畫面上(這個動作叫做 mount,也就是把東西放到 DOM 上面,讓瀏覽器 render 出來)

每當我更新 state,React 就會 re-render,也就是「會再呼叫一次 App() 這個 function」


#React







Related Posts

交叉編譯準備工作(一)

交叉編譯準備工作(一)

[進階 js 07] Temporal Dead Zone

[進階 js 07] Temporal Dead Zone

JavaScript 迴圈 -親愛的,我逃不出這個循環

JavaScript 迴圈 -親愛的,我逃不出這個循環


Comments