React 兩大重點
- Component 組件、元件
- 資料 vs 畫面
資料跟畫面分開看
如果只想要資料的話就不用整個 html 一起傳,只要傳資料的部分就好
資料跟畫面要一致,改資料也改畫面或改畫面也改資料
畫面永遠由資料產生 UI = f(state)
UI (畫面) state (資料)
每次更新的時候就把畫面整個清空,再把資料重新 render 出來
先改變資料,再呼叫 render()
React
A javaScript library for buildind user interface
codesandbox:類似 codepen 的東西
一個 function 就是一個 component,render 的時候把 component 帶進去
function (props) {
<h1>hello, {props.name}</h1>
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Hello name="hi"/>, // 帶入參數
rootElement
);
環境建置
- 從頭建
- 用現成的
用現成的 Create a New React App:
- 安裝 (要等很久)
npm init react-app my-app
- 在 http://localhost:3000 跑起來
安裝好的檔案:npm run start
- README:教你怎麼跑起來
- package.json:用了甚麼 dependences
- src -> index.js:render app.js
<React.StrictMode>
:嚴格模式,可以先拿掉 - src -> app.js:寫 code 的地方
React Component
- funciton + 大寫開頭的名稱
- return 後面要接東西,如果換行要用 ( ) 包起來
- 用閉合標籤把 component 放入 render,如果有要帶參數再用成對標籤
- 用 { } 把 JS 相關的包起來
ex.
- Controller component :value 是放在 state 裡面
- Uncontrolled component:value 沒有放在 state 裡面
React style
- inline style
- 在 app.css 寫 css
className:帶入 class name - 使用套件 styled-components
Styled-components
安裝套件:styled-components
npm install --save styled-components
引入套件
import styled from 'styled-components'
其他參考::styled-components 文件筆記
JSX
- React 可以解讀 JSX 的內容
- 可以在 HTML 中放入 JS 的變數,更方便的對 HTML 進行修改跟操作
- 通過 babel 轉成 JS (Babel Try it out)
<Button>Hello</Button>
// babel
React.createElement(Button, null, "Hello");
- 第二個參數是 props,第三個參數是 children
- JSX 沒辦法用迴圈、if...else...,不會有回傳值的東西不能放到 JSX 中
- 在 JSX 中,用 { } 包住 JS 的變數
- class 是關鍵字,要改成 className
- 自動 escape,輸入的東西是變成
<a href=""></a>
裡面的連結就要注意(寫成:href=window.encodeURIComponent())
React Hook
- use 開頭
useState
:讓 react 知道有東西改變了,監控到改變就會自動更新畫面
const { useState } = React
const [變數, setter] = useState(資料的初始值)
useState 建立一個被監控的變數(要監控的資料),透過 setter (修改資料的方法) 改變變數的值,React 重新渲染畫面
State
- 所有在 UI 上會動的東西都有它的狀態(state)
- immutable
- 新增資料時,要重設一個新的 state,而不是直接去改舊的 state
useState
引入 react
import React from 'react'
在 function component 裡面寫
const [變數名稱, setter] = React.useState(狀態初始值) //是一個陣列,應用解構語法
變數名稱:state 的值
setter:一個 function,設定 state (非同步)
function App() {
const [todos, setTodos] = React.useState([
'吃飯', '睡覺', '打咚咚'
])
const handleButtonClick = () => {
setTodos([...todos, Math.random()])
}
return (
<Container>
<Title>Todo List</Title>
<Button onClick={handleButtonClick}>Add Todo</Button>
<Add_Items />
<Items_list>
{
todos.map((todo, index) => <Item key={index} content={todo} />)
}
</Items_list>
</Container>
);
}
口訣:
修改用 map()
刪除用 filter()
新增用解構語法
Todo List 總結:(React 基礎)
- component
- props 在元件自訂屬性,接收 props
- style - styled component
- Event handler,直接加在元件上面
- JSX
- state
useEffect
- 是 react 一個內建的 Hook (補充:hook 只能被寫在 component 的第一層)
- 每次 render 後,瀏覽器 paint 完,去做點甚麼(執行 useEffect 裡面的 function)
- 為甚麼需要這個 hook?
想在 render 完想做點甚麼,但又不想每次 render 完都去做,只想在某些 state 改變的時候才去做事情,ex. 想要在 render 完串一個 API,但又不想要每次 render (注:state 改變就會重新 render) 都去串 API
引入 useEffect
import { useState, useRef, useEffect } from "react";
使用 useEffect
- 第一個參數放函式
- 第二個參數放一個陣列,陣列裡面放關注的資料
當陣列裡面的東西有改變時,就再執行一次 useEffect
(若只想在第一次 render 完執行 useEffect,就放一個空陣列)
useEffect(() => {
console.log('執行完畢') // 要執行的東西
}, [關注的資料]) // 有改變才會再執行 useEffect
ex. 當 todos 有改變的時候,執行 useEffect
useEffect(() => {
writeTodosToLocalStorage(todos);
console.log(JSON.stringify(todos))
}, [todos]);
這樣寫有個問題,就是第一次畫面出來是原先的資料,這時候執行 useEffect 去拿 localStorage 的資料,資料改變後又在 render 一次,造成畫面閃了一下
補充:
localStorage 可以跨瀏覽器使用,資料不會消失,永久保存
localStorage.setItem(key, value)
:存入資料
localStorage.getItem(key, value)
:取出資料
localStorage.removeItem(key)
:移除資料
從 DevTool 查看 Application 的 Local Stoage 可以找到儲存的資料
畫面會閃一下,有兩個解法:
- useLayoutEffect:在 render 後,瀏覽器 piant 之前執行,提早更新
- lazy initializer:
- 為了解決每次 render useState 的初始值不要重新設定,因為會浪費效能
- 在 useState( ) 裡用一個 function 回傳想要的資訊,function 只會執行一次
- 複雜的事情做一次
相關參考:React Hook flow 圖片
自己寫一個 hook
用自己寫的 hook 把其中的邏輯抽出來,再引入進去
- 新增 hook 的檔案,要 use 開頭 ex. useInput.js
- 引入 useState,把要用 useState 寫的東西寫在這個檔案,然後 export 出去
// 這個檔案是 useInput.js import { useState } from "react"; // 引入 useState // useState 要做的事寫在自訂的 function 裡,再把它 export export default function useInput() { const [value, setValue] = useState(""); const handleChange = (e) => { setValue(e.target.value); }; // 回傳使用這個 hook 時會用到的東西 return { value, setValue, handleChange, }; }
- 在 app.js 裡面把自己寫的 hook 引入進去使用
import useInput from "./useInput"; const { value, setValue, handleChange } = useInput();
React 的渲染機制
渲染機制 Reconciliation
Recociliation:React 的運行機制,用 Virtual DOM (虛擬 DOM) 快速找出不同的地方,舊的 V-DOM 跟新的 V-DOM 進行比對,比對改了哪些地方,再應用到真的 DOM
React 機制達成目標:
- 改變 state,畫面就會改變
- 讓 virtual dom render 出想要的形式 (ex. 瀏覽器、手機、投影片)
react 事件代理機制是綁在 root 的節點
re-render
parent re-render,底下的 children 也會 re-render
如何避免 re-render:
使用 memo
搭配 useCallback
,memo 作用是如果 props 都沒有改變就不會 re-render,useCallback 作用是把 function 記起來,使用第一次存好的 function
每個 render 都只看得到自己這次 render 的值
useMemo
event pooling
class component
16.8 之前的東西,16.8 之後 hook 出現,function componenet 可以使用 state
怎麼寫一個 class component
class Button extends React.Component {
render() {
const { onClick, children } = this.props;
return <AddBtn onClick={onClick}>{children}</AddBtn>
}
}
prop drilling & context
prop drilling
上層的東西傳到下層,一層層傳下去,經過好幾層才能傳到,如果中間層出現問題就可能會傳不到
useContext
解決 prop drilling 可能出現的問題
想成脈絡的感覺,讓中間層可以不用傳東西最底層也能拿到
- const DemoContext =
createContext()
:建立 Context - <DemoContext
.Provider value={setter}
></DemoContext.Provider
>:Provider 把下層的東西包住,並把要傳遞的值帶到 value(value 的值可以動態改變),被包在裡面的東西都可以取得 value - const setter =
useContext(DemoContext)
:把要傳遞的值設成 context
prop types
在跟目錄建立一個檔案 eslintrc.json,在檔案裡面寫:
{
"extends": ["react-app"],
"rules": {
"react/prop-types": "warn"
}
}
CLI 會提醒你哪些地方可以修改
補充:
在前面加上 $,就不會影響到結構,可以用在只想當作 styled component prop 使用
Prittier
幫你決定寫 code style 的工具
commit 之前跑 lint-staged,符合的檔案跑 prittier
測試
React testing library
測試 react component
App.test.js => 測試檔
// 跑測試
npm run test
Cypress
- end to end 的 test
- 會跑一個 browser 起來
- 接近真實使用者在用網站的感受
- 安裝
npm install cypress
- 在 package.json 的 scripts 加上
"cypress:open": "cypress open"
- npm run cypress open
- 刪掉 integration 裡面的內建檔案
- 在 integration 新增測試檔 home.spec.js
- 在 cypress.json 裡面加上 baseURL
{ "baseUrl":"網站的網址" }
- 在 home.spec.js 加上
describe('The Home Page', () => { it('successfully loads', () => { cy.visit('/') }) })
- 在 successfully 裡面 mock 結果,指定 call api 的時候回傳的資料
// 先在 cypress.json 加上 experimentalFetchPolyfill: true
cy.route("APIurl", [ { // 指定資料 id: 1, title: "hello", createdAt: 12345, } ])