type
Post
status
Published
date
Feb 27, 2020
slug
summary
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
tags
Web dev
ReactJS
category
技术分享
icon
password

0.什么是 React Hooks

Hooks 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

1.为什么有 React Hooks

https://zh-hans.reactjs.org/docs/hooks-intro.html#motivation
  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的 class

2.怎么用 React Hooks

1.基础

基本用法看官网即可 https://zh-hans.reactjs.org/docs/hooks-reference.html
  • Basic Hooks
    • useState
    • useEffect
    • useContext
  • Additional Hooks
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue
  • Custom Hooks

2.进阶:

useState:

  1. 陈旧闭包( stale closure https://leewarrick.com/blog/react-use-effect-explained/, https://zh-hans.reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function )
思考下面的代码
function Counter() { const [count, setCount] = useState(0); function handleAlertClick() { setTimeout(() => { alert('You clicked on: ' + count); }, 3000); } useEffect(() => { console.log(`You clicked ${count} times`); document.title = `You clicked ${count} times`; }, []); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> <button onClick={handleAlertClick}>Show alert</button> </div> ); }
function Timer() { const [count, setCount] = useState(0); const [randomNum, setRandomNum] = useState(0); useEffect(() => { const intervalId = setInterval(() => { setCount(count + 1); setRandomNum(Math.random()); }, 1000); return () => clearInterval(intervalId); }, []); return ( <div> <p>The count is: {count}</p> <p>RandomNum is {randomNum}</p> </div> ); }
  1. 太多的 useState, state 写成对象可不可以:可以,但需自己手动合并更新对象 https://zh-hans.reactjs.org/docs/hooks-reference.html#functional-updates
setState( prevState => { // 也可以使用 Object.assign return { ...prevState, ...updatedValues }; } );
useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象。

useEffect:

与生命周期对应
  1. componentDidMount
componentDidMount() { fn() } useEffect(() => fn(), []);
  1. componentWillUnmount
componentWillUnmount() { fn2() } useEffect(() => fn2, []);
// 结合看看 componentDidMount() { fn() } componentWillUnmount() { fn2() } useEffect(() => { fn() return () => fn2() }, []);
  1. componentDidUpdate
const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } });
模拟生命周期只是方便初学 hooks 的人快速理解,但想要深入理解,我们不应该用生命周期的方式看待 useEffect( https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/ )
notion image
image
总结: https://github.com/dt-fe/weekly/blob/v2/096.%E7%B2%BE%E8%AF%BB%E3%80%8AuseEffect%20%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97%E3%80%8B.md
  1. 每次 Render 都有自己的 Props 与 State
  1. 每次 Render 都有自己的事件处理
  1. 每次 Render 都有自己的 Effects

useLayoutEffect

官网讲了作用,这里给一个实际的例子

useMemo 与 useCallback:

  1. 作用演示
  1. 使用 useMemo 实现 useCallback
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

useRef

类似实例变量的东西 https://zh-hans.reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
  1. useRef 解决 stale closure 问题
function Timer() { const [count, setCount] = React.useState(0); const countRef = React.useRef(0); React.useEffect(() => { const intervalId = setInterval(() => { countRef.current = countRef.current + 1; setCount(countRef.current); }, 1000); return () => clearInterval(intervalId); }, []); return <div>The count is: {count}</div>; }
  1. 但不能没有 useState
// This won’t work: function Timer() { const count = React.useRef(0); React.useEffect(() => { const intervalId = setInterval(() => { count.current = count.current + 1; //console.log('Ref example count: ' + count.current) }, 1000); return () => clearInterval(intervalId); }, []); return <div>The count is: {count.current}</div>; }

有了 useReducer 是不是不需要 redux 了?

一定程度上是,但对于我们平时做的项目来说还有很多事要做。
useReducer + useContext + useEffect 才能替代 Redux + Connect + Redux 中间件方案

3.React Hooks 原理

“手写 Hooks”

useState useEffect useMemo
import React from 'react'; import ReactDOM from 'react-dom'; let Hook = []; let currentHook = 0; function useState(initVal) { Hook[currentHook] = Hook[currentHook] || [, initVal]; const cursor = currentHook; function setVal(newVal) { Hook[cursor] = [, newVal]; render(); } // 返回state 然后 currentHook+1 return [Hook[currentHook++][1], setVal]; } function useEffect(fn, watch) { const hasWatchChange = Hook[currentHook] ? !watch.every((val, i) => val === Hook[currentHook][1][i]) : true; if (hasWatchChange) { fn(); Hook[currentHook] = [, watch]; } currentHook++; // 累加 currentHook } function useMemo(fn, watch) { const hasWatchChange = Hook[currentHook] ? !watch.every((val, i) => val === Hook[currentHook][1][i]) : true; if (Hook[currentHook] && !hasWatchChange) { const prev = Hook[currentHook][0]; currentHook++; return prev; } const r = fn(); Hook[currentHook] = [r, watch]; currentHook++; // 累加 currentHook return r; } function App() { const [count, setCount] = useState(0); const [data, setData] = useState(1); useEffect(() => { document.title = `You clicked count ${count} times`; }, [count]); useEffect(() => { document.title = `You clicked data ${data} times`; }, [data]); const dataPlus = useMemo(() => { console.log('data cal'); return data; }, [data]); return ( <div> <button onClick={() => { setCount(count + 1); }} >{`当前点击次数:${count}`}</button> <button onClick={() => { setData(data + 2); }} >{`当前点击次数+2:${data}`}</button> <button onClick={() => { setData(5); }} > set data = 5 </button> <div>dataPlus(data + 1): {dataPlus}</div> </div> ); } render(); // 首次渲染 function render() { currentHook = 0; // 重新render时需要设置为 0 ReactDOM.render(<App />, document.getElementById('root')); console.log(Hook); // 执行hook后 数组的变化 }
  1. “setState”(useState 中的第二个返回值统称)才会更新
  1. 维护一个存储所有 Hooks 的数据结构,一个一个 Hook 处理,处理完递增
  1. 对依赖的存储和比较
手写代码的问题:
  1. 数据结构:数组 vs 链表
  1. render()部分的具体实现:如何与当前 FC 绑定而不是 Render All?获取当前 fiber
源码: https://github.com/facebook/react/blob/master/packages/react/src/ReactHooks.js
https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberHooks.js

Memoization

function memoize(fn) { return function() { var args = Array.prototype.slice.call(arguments); fn.cache = fn.cache || {}; return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this, args)); }; }

4.React Hooks 不足

  1. 暂时不能取代的 API:
getSnapshotBeforeUpdate, getDerivedStateFromError, componentDidCatch
可能会有 useCatch(() => {})
  1. 难维护? https://weibo.com/1400854834/I6psH4P6Q?filter=hot&root_comment_id=0&type=comment

6.参考

  • 官网:https://zh-hans.reactjs.org/docs/hooks-intro.html
  • 丹·阿布莫夫博客:https://overreacted.io/zh-hans/a-complete-guide-to-useeffect/

7.实例

https://www.jianshu.com/p/a47c03eaecba
Optional Chain & Nullish Coalescing OperatorECharts