React


Posted by chihyu on 2021-01-25

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

  1. 安裝 (要等很久)
    npm init react-app my-app
    
  2. 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 基礎)

  1. component
  2. props 在元件自訂屬性,接收 props
  3. style - styled component
  4. Event handler,直接加在元件上面
  5. JSX
  6. 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 把其中的邏輯抽出來,再引入進去

  1. 新增 hook 的檔案,要 use 開頭 ex. useInput.js
  2. 引入 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,
    };
    }
    
  3. 在 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 起來
  • 接近真實使用者在用網站的感受
  1. 安裝
    npm install cypress
    
  2. 在 package.json 的 scripts 加上
    "cypress:open": "cypress open"
    
  3. npm run cypress open
  4. 刪掉 integration 裡面的內建檔案
  5. 在 integration 新增測試檔 home.spec.js
  6. 在 cypress.json 裡面加上 baseURL
    {
    "baseUrl":"網站的網址"
    }
    
  7. 在 home.spec.js 加上
    describe('The Home Page', () => {
    it('successfully loads', () => {
     cy.visit('/')
    })
    })
    
  8. 在 successfully 裡面 mock 結果,指定 call api 的時候回傳的資料
    // 先在 cypress.json  加上
    experimentalFetchPolyfill: true
    
    cy.route("APIurl", 
    [
    { // 指定資料
     id: 1, 
     title: "hello",
     createdAt: 12345,
    }
    ])
    

#Web #React #framework







Related Posts

[練習] JSONP 實作

[練習] JSONP 實作

ActionResult

ActionResult

ES6(Default Parameters、箭頭函式、Import 與 Export、Babel)

ES6(Default Parameters、箭頭函式、Import 與 Export、Babel)


Comments