一袭红衣 發表於 2020-5-4 22:04:00

React Hooks总结

<h2>Hook 前言</h2>
<h3>什么是Hook</h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">自从 16.8 版本开始,hooks 的出现使得你可以在不编写 class 的情况下使用状态管理以及其它 React 的特性。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">那么在 React Hooks 出现之前,class 类组件和 function 函数组件有什么区别?Hooks 出现之后,函数组件又是如何满足原来只有类组件才有的功能的?</span></p>
<h4>1.类组件和没有 hooks 加持的函数组件:</h4>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">函数组件常被称为无状态组件,意思就是它内部没有状态管理,只能做一些展示型的组件或者是完全受控组件。因此差别主要体现在:</span></p>
<ul>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">函数组件没有内部状态管理</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">函数组件内部没有生命周期钩子</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">函数组件不能被获取组件实例 ref,函数组件内也不能获取类组件的 ref</span></li>
</ul>
<h4>2.类组件和有 hooks 加持的函数组件:</h4>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">有了 hooks 加持之后,函数组件具备了状态管理,除了可以使用内置的 hooks ,我们还可以自定义 hooks。</span></p>
<ul>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">类组件有完备的生命周期钩子,而函数组件只能具备:DidMount / WillUnmount / DidUpdate / willUpdate</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">函数组件内部可以通过内置 hook 获取类组件 ref,也可以通过一些 API 的组合使用达到获取函数组件 ref 的功能</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">函数组件具备了针对状态变量的 setter 监听(类似于 vue watch),类组件没有这种 API。(useCallback、useEffect、useMemo等)</span></li>
</ul>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">类组件原本比函数组件更加完整,为什么还需要 hooks?</span></h3>
<h4><span style="font-family: &quot;Microsoft YaHei&quot;">这要说到 React 的设计理论:</span></h4>
<ul>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">React 认为,UI 视图是数据的一种视觉映射,即 UI = F(DATA) ,这里的 F 需要负责对输入的数据进行加工、并对数据的变更做出响应</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">公式里的 F 在 React 里抽象成组件,React 是以组件为粒度编排应用的,组件是代码复用的最小单元</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">在设计上,React 采用 props 来接收外部的数据,使用 state 属性来管理组件自身产生的数据(状态),而为了实现(运行时)对数据变更做出响应需要,React 采用基于类 Class 的组件设计</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">除此之外,React 认为组件是有生命周期的,因此开创性地将生命周期的概念引入到了组件设计,从组件的 create 到 destroy 提供了一系列的 API 共开发者使用</span></li>
</ul>
<h4><span style="font-family: &quot;Microsoft YaHei&quot;">类组件 Class Component 的困局</span></h4>
<h6><span style="font-family: &quot;Microsoft YaHei&quot;">组件状态逻辑复用困局</span></h6>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">对于有状态组件的复用,React 团队和社区尝试过许多方案,早期使用 CreateClass + Mixins,使用 Class Component 后又设计了 Render Props 和 HOC,再到后来的 Hooks设计,React 团队对于组件复用的探索一直没有停止。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">HOC 和 Render Props 都有自己的缺点,都不是完美的复用方案(详情了解 React HOC 和 Render Props),官方团队认为应该为共享状态逻辑提供更好的</span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">原生途径。在 Hooks 加持后,功能相对独立的部分完全抽离到 hook 实现,例如网络请求、登录状态、用户核验等;也可以将 UI 和功能(状态)分离,功能放到 hook 实现,例如表单验证。</span></p>
<h6><span style="font-family: &quot;Microsoft YaHei&quot;">复杂组件变得难以理解</span></h6>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">我们经常维护一些组件,它们起初很简单,但是逐渐会被状态逻辑和副作用充斥。在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了挑战。Hook 可将组件中相互关联的部分拆分成更小的函数</span></p>
<h6><span style="font-family: &quot;Microsoft YaHei&quot;">JavaScript Class 的缺陷</span></h6>
<ul>
<li><span style="font-family: &quot;Microsoft YaHei&quot;">this的指向问题(语言缺陷)</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;">编译后体积和性能的问题</span></li>
</ul>
<p>同样功能的类组件和函数组件,在经过 Webpack 编译后体积相差明显,也伴随着一定的性能问题。这是因为 class 在 JavaScript 中本质是函数,在 React 内部也是当做 Function类 来处理的。而函数组件编译后就是一个普通的 function,function 对 JS 引擎是友好的。</p>
<h2>内置 Hooks</h2>
<h3>useState</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const = useState(initialState);</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">用来承担与类组件中的 state 一样的作用,组件内部的状态管理</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;collapse:true;;gutter:true;">function () {
const [ count, setCount ] = useState(0);
const onClick = ()=&gt; {
    setCount( count + 1 );
    // setCount(count =&gt; count + 1);
};
   
return &lt;div onClick={onClick}&gt;{ count }&lt;/div&gt;
}</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">除了直接传入最新的值,还可以函数式更新,这样可以访问到先前的 state。如果你的初始 State 创建比较昂贵时,可以传一个函数给 useState:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;collapse:true;;gutter:true;">function Table(props) {
// ⚠️ createRows() 每次渲染都会被调用
const = useState(createRows(props.count));
// ...
}

function Table(props) {
// ✅ createRows() 只会被调用一次
const = useState(() =&gt; createRows(props.count));
// ...
}</pre>
</div>
<blockquote>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">如果是复杂类型的 state,需要传入修改后的完整的数据,不再像类组件中的 setState 可以自动合并对象,需要手动合并:</span></p>
<p class="brush:javascript;gutter:false;"><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">setState(prevState =&gt; ({...prevState, ...updatedValues}));</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">此外,useReducer 是另一种可选的方案。</span></p>
</blockquote>
<h3>useEffect</h3>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">useEffect(func, );</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">可以用来模拟生命周期,即可以完成某些副作用。什么叫副作用?一般我们认为一个函数不应该对外部产生影响,一旦在函数内部有某些影响外部的操作,将其称之为副作用。例如改变 DOM、改变 Window对象(Global)、设置定时器、使用原生API绑定事件等等,如果处理不好,它们可能会产生 bug 并产生破坏。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">如果只传一个参数,每次组件渲染都会执行回调函数(挂载+跟新),相当于 componentDidMount() + componentDidUpdate()</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">返回值函数:在组件更新前、组件卸载时执行,相当于 componentWillUnmount() + componentWillUpdate()</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;collapse:true;;gutter:true;">useEffect(() =&gt; { // 每次渲染后执行此函数,获取到的值是最新的
    console.log("Effect after render", count);
    return () =&gt; { // 每次执行useEffect前,先执行此函数,获取到的数据是更新之前的值
      console.log("remove last", count);
    }
});</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">第二个参数是依赖列表,当依赖的状态数据发生改变时会执行回调</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">1.如果是一个空数组,表示没有依赖项</span></p>
<ul>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">回调函数:只在组件挂载的时候执行一次,相当于 componentDidMount()</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">返回值函数:只在组件卸载的时候执行一次,相当于 componentWillUnmount()</span></li>
</ul>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">2.如果有值</span></p>
<ul>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">回调函数:除了具有 componentDidMount(),还当 数组内的变量发生变化时执行 componentDidUpdate()</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">返回值函数:除了具有 componentWillUnmount(),还当 数组内的值发生变化时执行 componentWillUpdate()</span></li>
</ul>
<blockquote>
<p>需要注意的是,</p>
<p>&nbsp; &nbsp; 1.第二个参数的比较其实是浅比较,传入引用类型进去是无意义的<br>&nbsp; &nbsp; 2.一个组件内可以使用多个 useEffect,它们相互之间互不影响<br>&nbsp; &nbsp; 3.useEffect 第一个参数不能是 async 异步函数,因为它总是返回一个 Promise,这不是我们想要的。你可以在其内部定义 async 函数并调用</p>








</blockquote>
<h3>useLayoutEffect</h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">它与 useEffect 的用法完全一样,作用也基本相同,唯一的不同在于执行时机,它会在所有的 DOM 变更之后同步调用 effect,可以使用它来</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">useEffect 不会阻塞浏览器的绘制任务,它会在页面更新之后才执行。而 useLayoutEffect 跟 componentDidMount 和 componentDidUpdate 的执行时机一样,会阻塞页面渲染,如果当中有耗时任务的话,页面就会卡顿。</span><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">大多数情况下 useEffect 比 class 的生命周期函数性能更好,我们应该优先使用它。</span></p>
<blockquote>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件,则需要注意 useLayoutEffect<span style="font-family: &quot;Microsoft YaHei&quot;">&nbsp;</span>与 componentDidMount、componentDidUpdate&nbsp;的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect。</span></p>








</blockquote>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">useReducer</span></h3>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const = useReducer(reducer, initialArg, init);</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">useState 的替代方案,它接收一个 (state, action) =&gt; newState 的 reducer 处理函数,并返回当前的 state 和 配套的 dispatch 方法。使用方法与 redux 非常相似。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">某些场景下,useReducer 比 useState 更加适用:</span></p>
<ul>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">当状态变量比较复杂且包含多个子值的时候</span></li>
<li><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">下一个 state 依赖之前的 state</span></li>
</ul>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;collapse:true;;gutter:true;">const initialState = {count: 0};

function init(initialCount) {
return {count: initialCount};
}

function reducer(state, action) {
switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
}
}

function Counter(props) {
const = useReducer(reducer, initialState);
// const = useReducer(reducer, props.initialCount, init);

return (
    &lt;&gt;
      Count: {state.count}
      &lt;button onClick={() =&gt; dispatch({type: 'decrement'})}&gt;-&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch({type: 'increment'})}&gt;+&lt;/button&gt;
    &lt;/&gt;
);
}</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">此外,它还可以模拟 forceUpdate()</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;collapse:true;;gutter:true;">const = useReducer(x =&gt; x + 1, 0);

function handleClick() {
   forceUpdate();
}</pre>
</div>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">useCallback</span></h3>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const memoizedCallback = useCallback(func, );</pre>
</div>
<p><span style="font-size: 15px"><span style="font-family: &quot;Microsoft YaHei&quot;">useCallback 缓存了方法的引用。它有的作用:</span><span style="font-family: &quot;Microsoft YaHei&quot;">性能优化,父组件更新,传递给子组件的函数指针不会每次都改变,只有当依赖项发生改变的时候才会改变指针。避免了子组件的无谓渲染</span></span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">它的本质是对函数依赖进行分析,依赖变更时才重新执行。</span></p>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">useMemo &amp; React.memo</span></h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">useMemo 用于缓存一些耗时的计算结果(返回值),只有当依赖项改变时才重新进行计算。</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">useCallback(func, )等同于useMemo(() =&gt; func, )</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">useCallback 缓存的是方法的引用,useMemo 缓存的是方法的返回值,适用场景都是避免不必要的子组件渲染。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">在类组件中有 React.PureComponent,与之对应的函数组件可以使用 React.memo,它们都会在自身 re-render 时,对每一个 props 项进行浅对比,如果引用没有发生改变,就不会触发渲染。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">那么,useMemo 和 React.memo 有什么共同点呢?前者可以在组件内部使用,可以拥有比后者更细粒度的依赖控制。它们两个与 useCallback 的本质一样,都是进行依赖控制。</span></p>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">useContext</span></h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">专门为函数组件提供的 context hook API,可以更加方便地获取 context 的值。</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const value = useContext(MyContext);</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;">useContext(MyContext) 接收一个 context 对象,当前获取到的值由上层组件中距离最近的 &lt;MyContext.Provider&gt; 的 value 决定。<br></span></p>
<blockquote>
<p>useContext(MyContext)&nbsp;相当于之前的 static contextType = MyContext 或者 &lt;MyContext.Consumer&gt;</p>



























</blockquote>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">useRef</span></h3>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;gutter:true;">const refContainer = useRef(initialValue);</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">useRef 返回一个可变的 ref 对象,其 current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">注意:此 hook 可以获取 DOM 元素、类组件示例,但无法获取函数组件实例,因为函数组件根本没有实例。如果想让函数组件被获取到 ref,可以使用 useImperativeHandle 来达到这样的效果</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;"><span style="font-size: 15px">另外,useRef 获取到的“ref”对象是一个 current 属性可变且可以容纳任意值的通用容器。可以实现如下功能:</span></span></p>
<ul>
<li><span style="font-size: 15px; font-family: &quot;Microsoft YaHei&quot;">模拟实例变量</span></li>
<li><span style="font-size: 15px; font-family: &quot;Microsoft YaHei&quot;">获取 prevProps、prevState</span></li>
</ul>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;collapse:true;;gutter:true;">// 当做 class 实例变量
function Timer() {
const intervalRef = useRef();

useEffect(() =&gt; {
    const id = setInterval(() =&gt; {
      // ...
    });
    intervalRef.current = id;
    return () =&gt; {
      clearInterval(intervalRef.current);
    };
});

// ...
}

// 获取prevProps,prevState
function Counter(props) {
const = useState(0);
const prevProps = useRef(props);
const prevCount = useRef(count);

useEffect(() =&gt; {
    prevCount.current = count;
    prevProps.current = props;
});

return &lt;h1&gt;Now: {count} - {props}, before: {prevCount.current} - {prevProps.current}&lt;/h1&gt;;
}</pre>
</div>
<h3><span style="font-family: &quot;Microsoft YaHei&quot;">useImperativeHandle</span></h3>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">useImperativeHandle 可以让你在使用 ref 时自定义对外暴露的属性。官方指出,它应当与 forwardRef 一起使用。</span></p>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">示例:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:javascript;collapse:true;;gutter:true;">function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () =&gt; ({
    focus: () =&gt; {
      inputRef.current.focus();
    }
}));
return &lt;input ref={inputRef} ... /&gt;;
}
FancyInput = forwardRef(FancyInput);</pre>
</div>
<p><span style="font-family: &quot;Microsoft YaHei&quot;; font-size: 15px">此时,通过 ref 获取到 FancyInput 的"实例",其 current 属性内只有 foucs 属性可供访问</span></p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/V587Chinese/p/12778842.html
頁: [1]
查看完整版本: React Hooks总结