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:
- 陈旧闭包( 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> ); }
- 太多的 useState, state 写成对象可不可以:可以,但需自己手动合并更新对象 https://zh-hans.reactjs.org/docs/hooks-reference.html#functional-updates
setState( prevState => { // 也可以使用 Object.assign return { ...prevState, ...updatedValues }; } );
useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象。
useEffect:
与生命周期对应
- componentDidMount
componentDidMount() { fn() } useEffect(() => fn(), []);
- componentWillUnmount
componentWillUnmount() { fn2() } useEffect(() => fn2, []);
// 结合看看 componentDidMount() { fn() } componentWillUnmount() { fn2() } useEffect(() => { fn() return () => fn2() }, []);
- 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/ )
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
- 每次 Render 都有自己的 Props 与 State
- 每次 Render 都有自己的事件处理
- 每次 Render 都有自己的 Effects
useLayoutEffect
官网讲了作用,这里给一个实际的例子
useMemo 与 useCallback:
- 作用演示
- 使用 useMemo 实现 useCallback
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
useRef
类似实例变量的东西 https://zh-hans.reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
- 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>; }
- 但不能没有 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后 数组的变化 }
- “setState”(useState 中的第二个返回值统称)才会更新
- 维护一个存储所有 Hooks 的数据结构,一个一个 Hook 处理,处理完递增
- 对依赖的存储和比较
手写代码的问题:
- 数据结构:数组 vs 链表
- 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 不足
- 暂时不能取代的 API:
getSnapshotBeforeUpdate
, getDerivedStateFromError
, componentDidCatch
可能会有 useCatch(() => {})
- 难维护? 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
- 作者:Wave52
- 链接:https://vercel.wuchengran.com/article/b6f68665-7fd0-4f2d-b2cf-b6282e383dec
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章