React - Lauviah0622/Lavi-Note GitHub Wiki

React

a11y

aria-tag

可以在 JSX 裡面用 aria-tag

不過要用 kebab-case

<input type="tes" aria-label={labelText}></input>

Fragment

component 頂層必須要有一個東西包著,上面不能空空的

如果沒有 div 的話就要用 <Fragment></Fragment>

import React, { Fragment } from "react";

function ListItem({ item }) {
  return (
    <Fragment>
      <dt>{item.term}</dt>
      <dd>{item.description}</dd>
    </Fragment>
  );
}

不然就要用 <></>

這兩個的差別在於 Fragment 可以放 props,但是 簡寫的不行

除此之外簡寫的方式要另外的 babel

表單的 For attribute

在原本的 html 裡面

<label for="input">label of input</label>
<input id="input"/>

但是在 react 裡面,要用 HTMLfor

<label htmlFor="input">label of input</label>
<input id="input"/>

為什麼要用 htmlFor ???

無障礙的使用 tab

用 react 因為 DOM 會變,所以 focus 會跑掉,要把 focus 記起來

所以需要用 ref 去決定怎麼 focus

這個 library 可以處理 modal

eslint-plugin-jsx-a11y

可以用這個 eslint 來檢查 JSX 的 a11y

Code splitting

用 bundle 工具把 code 分開,建立多個 bundle,然後再 runtime 動態的被載入

向下面這樣已非同步的方式來 import module 就可以做到 code split

import("./math").then((math) => {
  console.log(math.add(16, 26));
});

CRA 的介紹 https://create-react-app.dev/docs/code-splitting/

Webpack 的設定 https://webpack.js.org/guides/code-splitting/

React.Lazy

React.Lazy 這個東西是要配合 Suspense 去做使用

import React, { Suspense } from "react";

const OtherComponent = React.lazy(() => import("./OtherComponent"));
const AnotherComponent = React.lazy(() => import("./AnotherComponent"));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

suspense 會監聽裡面的 lazy 有沒有 ok 被宰入,還沒被載入那就會等到他載入再用

使用情境

最常使用的地方應該就是 route 了,沒有 route 到的地方其實不太需要立即踩入 https://reactjs.org/docs/code-splitting.html#route-based-code-splitting

import React, { Suspense, lazy } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

const Home = lazy(() => import("./routes/Home"));
const About = lazy(() => import("./routes/About"));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
      </Switch>
    </Suspense>
  </Router>
);

像這樣的狀況就不會再第一次 Render 的時候載入 component,後來才會發 request 去拿 component 的 JS

lazy 這個東西只接收一個 Promise,然後他只有在 resolve 的 Promise 的時候才會拿到 裡面的 module(也就是 component)

如果 Promise reject 就會報錯

suspense 還有 lazy 這個組合外面一定要 包一個 error boundary

不過 lazy 不是 async 嗎?? error boundary 這個東西包有什麼用? 感覺這部分可當作例外, 除了 lazy 裡面的 async 錯誤其他都會被抓出來, 我猜 lazy 裡面有把 promise 解開然後再丟 Error 的機制吧

Named Export

React.lazy 目前只支援 default

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;

像這樣一個檔案裡面有多個 component

// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";

就要用另外一個檔案抽成 default,然後再被引入

// MyApp.js
import React, { lazy } from "react";
const MyComponent = lazy(() => import("./MyComponent.js"));

Component 跟 Object

在 react 裡面,所有的 JSX 都會被執行然後變成 virtual DOM

但是你寫的 component

function Component() {
  return <div></div>
}

這個東西並不是 virtual DOM,而是 function

要以這樣的形式才會變成 virtual DOM

<Component />

所以 compoennt render 出來的東西是 object

function Component() {
  return <div></div>;
}

function Parent() {
  return (
    <div>
      <Component />
    </div>
  );
}

但是也可以用 function 的方式傳,優點是更加靈活?不太確定

function 的寫法的確是更加靈活, 所以會用在比較長重新生成的狀態

如果直接船 component 進去的話,那就沒辦法做變動

Error Boundaries

最主要解決的問題是不要讓錯誤影響到其他 component

一個 class component (目前這個東西只在 class Component 裡面有 API 可以用)

實務上,大部分的時間你只會想要宣告錯誤邊界 component 一次,然後在你的應用程式裡重複使用它 基本上也是就是宣告一個 error boundary 然後改變他的 fallback 就好了

可以想成 component 版本的 catch(),避免說 catch 去影響到其他部分, 在 JS 裡面就是影響到 main thread

放在那要放在頂層


Question 可以 catch 的 error 有哪些

可以 catch 到哪裡的 error?

asnyc ?會有這樣的問題嗎??


不會處理的錯誤

官方有寫一個 note:

Error boundaries 不 catch 這些 errro

  • event handler
  • async code,
  • SSR
  • error boundary 自己的 error

未被捕捉到的 error

這個改變有重要的意義。在 React 16,沒有被錯誤邊界所捕捉到的錯誤會 unmount 整個 React component tree。

這個的意思不是說只把那個 component unmount 掉而已

React dev 的錯誤訊息

是利用這個套件:@babel/plugin-transform-react-jsx-source 不喜歡可以弄掉

Ref

情境

  • 處理 focus, 選擇 text, mesida playback
  • 觸發動畫
  • 使用第三方 library

ref 的本質

與其說是 ref,不如說是 JSX ref 這個屬性的本質。他就是一個在 Component/ DOM element 在 Render 過程中 mount/unmount 會執行的 callback

用法

  1. 建立 ref

callback Ref:會在 mount, unmount 時被執行,而參數帶入 attr

  • mount 時 => callback(instance)
  • unmount 時 => callback(null)

Object Ref: 就是一個 Object,而這個 object 為了避免 reference 改變導致 rerender,所以儲存的資訊放在 object.current 裡面,這個 object 在 ref 的 callback 中會被綁定到最新的 instance

在 class 上建立:

class Comp extends Component {
  constructor(props) {
    this.ref = React.createRef()
  }
  render() {
    return ()
  }
}

在 function 上建立

function Comp {
  const ref = useRef()
  return ()
}
  1. 代入,被執行使用

Class Component:可綁定到 Class Component 的 instance

<ClassComp ref={ref}/>

DOMelement:可以綁定到 DOM element 本身

<form ref={ref}>

Fucntion Component:可以綁定到 FuncComp 內部 useImperativeHandle 中 callBack 的 return 值,再藉由閉包存取內部的變數

function FuncComp({funcRef}) {
  useImperativeHandle(funcRef, () => FuncCompMethod, )
}

<FuncComp funcRef={ref}/>

傳遞 ref

把 ref Pass 到 child,再交由 child 綁定在內部元素:

function Parent() {
  const childRef = useRef()
  return <Child childRef={childRef}/>
}

function Child({childRef}) {
  return <div>
    <input ref={childRef}>
  </div>
}

child 上不能使用 ref 這個屬性來傳遞 ref,因為會執行 callback 而進行綁定,真的要用的話要配合 FowardRef

function Parent() {
  const childRef = useRef()
  return <Child ref={childRef}/>
}

function Child({props, ref) {
  return <div>
    <input ref={ref}>
  </div>
}

注意事項

from here

除非你正在做延遲初始化,避免在 render 時設定 ref — 這可能會造成非預期的行為。

  • 有這種情形嗎?

HOC

HOC 是 react 的一個 pattern,本身是一個 function,用以把邏輯抽出來,內部以 container 包裹的方式來幫 component 新增功能

HOC 本身是一個 會 return Component 的 function

HOC(Component) => NewComponent

在內部做了這樣的事情

HOC 的 container 做了

  • 實作內部的邏輯
  • 傳遞 props 並 render 作為參數的 component

整體上來說,做到了分離邏輯並重用

HOC 寫法

function print(setting) {
  return function (msg) {
    return class PrintHOC extends React.Component {
      constructor(props) {
        super(props);
      }

      componentDidUpdate() {
        console.log("hoc1 update");
      }

      render() {
        return (
          <div>
            {msg}
            <Component {...this.props} />
          </div>
        );
      }
    };
  };
}

這樣的 hoc 可以做到HOC(config)(component),有需要的話可以把附加的功能切開,單獨取HOC(config),可以更加靈活

使用原則

  • 不要修改 component 如果是 class component,可以透過修改 instance.prototype 來做到修改原本 component 的內部邏輯,或者是 lifecycle。不要這麼幹,每個 HOC 修改來修改去很糟。

要做的話就在 hoc Contianer 裡面實作邏輯還有 lifecycle 就好,不要破壞 input Component 的內部抽象

  • 把剩下的 prop 傳入 input Component 並且盡可能的維持 component 的 interface 一致(簡單說,盡量不要從外部傳 props 給 container 用)

對外來說 使用的還是 component 本身,所以當然要傳 prop 給 input Component 用 而新增 props 這件事情也算是影響了原本的 input component 的 interface。盡量不要這麼做

  • 最大化相容性 也就是盡可能做到剛剛
hoc(config)(component)

這種形式。

而不是這樣

hoc(config, component)

這種形式的好處是你可以把 hoc funciton 跟 component 分成兩個變數

當你有很多的 hoc 的時候,就可以包成一個大 hoc 再用

const enhance =  composeFeature(hoc1, hoc2(config2), hoc3(config));
// composeFeature: (attr...) => (comp) => f(g(h(comp)))
const EnhancedComp(Component)

注意事項

  • 不要再 render 裡面用 HOC 基本上會自己報錯,再來是這樣每次的 component 都是新的,就會重新 render VDOM tree

  • static method

有時候套件會把 static method 放在 component 的 class 上包成一包 如果要幫套件再加上 HOC,這部分要自己在加上去

  • Ref 的傳遞也要注意

技巧

Render Props

hooks

useEffect

之前有疑惑過為甚麼不這樣寫,可以抽出來比較乾淨

const effect = () => {doEffect()}

function () {
  useEffect(effect)
}

這樣的寫法每次的 effect 會拿到一樣的 reference 如果你的 effect 內部沒有影響的話看起來是沒有關係 但是大部分的情況下

useEffect(() => {
  doEffect()
})

這樣每次都依照目前的 state slice 去建立新的 effect,就可以直接透過必包來拿到最新的 state,而不需要另外注入

如果把 effect 抽出來然後要拿到最新的 state 的話:

const effect: (state) => function
function () {
  const state
  useEffect(effect(state))
}
⚠️ **GitHub.com Fallback** ⚠️