慕林彬 發表於 2019-9-16 19:29:00

React Hooks 你不来了解下?

<h1 id="前言">前言</h1>
<p>最近在看 React 的新语法—— <code>React Hooks</code>,只能一句话概括:React 语法真的是越来越强大,越写代码越少。</p>
<p>强烈推荐还没看 React Hooks 的同学去学习下,这会让你写react 项目变得非常爽!</p>
<p>以前 React 组件可以看成是: 无状态组件(function定义)和有状态组件(class 定义),React Hooks 出现之后,我们基本所有的组件都可以用function定义,包括有组态组件,基本废除了 写 class 语法的 复杂性,让我们写代码真正变成了函数式编程。</p>
<h2 id="状态-hooksusestate">状态 Hooks(useState)</h2>
<p>这里说明一点,react中所有的Hooks都是一种函数,函数都是用来实现特定功能的。</p>
<p>useState 提供了创建组件state的功能,用法:</p>
<pre><code class="language-js">const = useState(0)
</code></pre>
<p>useState() 接受唯一一个状态初始值参数,返回包含状态和改变状态对应的函数的数组,这里采用 数组解构方法获得 状态变量 count ,改变状态方法 setCount。</p>
<p>强调一点:</p>
<p>useState() 传入的初始值不一定非要是个对象,可以为普通数据类型,比如:Number,String等,初始值用作组件初次渲染。</p>
<p>setCount() 接受一个全新的state状态,react会直接全部替换掉原来的state状态,这点和 setState() 有所不同。</p>
<p>Example:</p>
<pre><code class="language-js">import React, { useState } from 'react';
function Example(){
const [ count , setCount ] = useState(0);
return (
    &lt;div&gt;
      &lt;p&gt;You clicked {count} times&lt;/p&gt;
      &lt;button onClick={()=&gt;{setCount(count+1)}}&gt;click me&lt;/button&gt;
    &lt;/div&gt;
)
}
export default Example;

</code></pre>
<p>当把 Example 组件渲染到页面上时,可以通过点击按钮实时改变 count 状态,react 也会根据状态改变重新渲染页面。</p>
<h2 id="执行副作用操作hooksuseeffect">执行副作用操作Hooks(useEffect)</h2>
<p>之前在写 react 组件时,往往我们在组件的生命周期函数里面做一些额外的操作,比如发送ajax请求获取数据,清除定时器和异步执行任务等。</p>
<p>我们发现这样操作重复的代码很多,而且如果对于 react 生命周期钩子不熟悉的话,很容易出错,于是乎,useEffect() Hooks 出现了。</p>
<p><strong>useEffect() hooks 可以允许我们在 react 函数式组件中执行一些额外的副作用操作。</strong></p>
<p>在 React 组件中有两种常见副作用操作:</p>
<ul>
<li>
<p>需要清除的,比如开启的定时器,订阅外部数据源等,这些操作如果在组件消亡后不及时清除会导致内存泄漏。</p>
</li>
<li>
<p>不需要清除的,比如发起网络请求,手动变更 DOM,记录日志等。</p>
</li>
</ul>
<p>在 react 官网中有一段话很重要:</p>
<blockquote>
<p>如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。</p>
</blockquote>
<p>Example:</p>
<pre><code class="language-js">
import React, { useState , useEffect } from 'react';
function Example(){
const [ count , setCount ] = useState(0);
useEffect(()=&gt;{
    console.log(`You clicked ${count} times`)
})
return (
    &lt;div&gt;
      &lt;p&gt;You clicked {count} times&lt;/p&gt;
      &lt;button onClick={()=&gt;{setCount(count+1)}}&gt;click me&lt;/button&gt;
    &lt;/div&gt;
)
}
export default Example;
</code></pre>
<p>我们在刚才的例子上新增了一个功能,每次我们 count 状态改变的时候都会在控制台打印出我们点击的次数。可以看到使用 useEffect() Hooks 轻松实现。</p>
<p>强调一点:</p>
<ul>
<li>
<p>react首次渲染和之后的每次渲染都会调用一遍useEffect函数,而之前我们要用两个生命周期函数分别表示首次渲染(componentDidMonut)和更新导致的重新渲染(componentDidUpdate)。</p>
</li>
<li>
<p>useEffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数是异步执行的,而componentDidMonut和componentDidUpdate中的代码都是同步执行的。</p>
</li>
<li>
<p>可以为 useEffect() 传入第二个参数,它是一个数组,数组里面表示这个副作用的操作依赖的状态变量,换句话说:如果这个副作用的操作依赖的状态变量没有改变,则不会执行副作用操作。</p>
</li>
</ul>
<h2 id="组件之间传值hooksusecontext">组件之间传值Hooks(useContext)</h2>
<p>之前在用类声明组件时,父子组件的传值是通过组件属性和props进行的,那现在使用方法(Function)来声明组件,已经没有了constructor构造函数也就没有了props的接收,那父子组件的传值就成了一个问题。React Hooks 为我们准备了useContext。</p>
<p>useContext它可以帮助我们跨越组件层级直接传递变量,实现共享。</p>
<p>Example:</p>
<p>一:利用 createContext 创建上下文</p>
<pre><code class="language-js">
import React, { useState , createContext } from 'react';

// 创建一个 CountContext
const CountContext = createContext()

function Example(){
const [ count , setCount ] = useState(0);
return (
    &lt;div&gt;
      &lt;p&gt;You clicked {count} times&lt;/p&gt;
      &lt;button onClick={()=&gt;{setCount(count+1)}}&gt;click me&lt;/button&gt;
      {/* 将 context 传递给 子组件,context 值由value props决定*/}
      &lt;CountContext.Provider value={count}&gt;
      &lt;Counter/&gt;
      &lt;/CountContext.Provider&gt;
    &lt;/div&gt;
)
}
export default Example;
</code></pre>
<p>二:使用useContext 获取上下文</p>
<p>对于要接收context的后代组件,只需引入 useContext() Hooks 即可。</p>
<pre><code class="language-js">
function Counter(){
const count = useContext(CountContext)//一句话就可以得到count
return (&lt;h2&gt;{count}&lt;/h2&gt;)
}
</code></pre>
<p>强调一点:</p>
<p>useContext 的参数必须是 context 对象本身:</p>
<ul>
<li>正确: useContext(MyContext)</li>
<li>错误: useContext(MyContext.Consumer)</li>
<li>错误: useContext(MyContext.Provider)</li>
</ul>
<p>当组件上层最近的 &lt;MyContext.Provider&gt; 更新时,该 Hook 会触发重渲染,并使用最新传递给 &lt;MyContext.Provider&gt; 的 context value 值。</p>
<h2 id="处理更为复杂state结构的hooksusereducer">处理更为复杂state结构的Hooks(useReducer)</h2>
<p>前面我们使用的 useState Hooks可以为组件提供 state和操作改变state状态的方法,但往往有时候我们的state 结构更为复杂,例如 state 包含多个子值,或者下一个 state 依赖于之前的 state 等,这时候 useReducer 比 useState 更合适。</p>
<p>useReducer 的用法:</p>
<pre><code class="language-js">
const = useReducer(reducer, initialArg, init);
</code></pre>
<p>useReducer 接收一个形如 (state, action) =&gt; newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。</p>
<p>我们可以使用 useReducer 来重新写我们开篇计数器的demo:</p>
<p>Example:</p>
<pre><code class="language-js">
const initialState = {count: 0};

function reducer(state, action) {
switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      return state
}
}
// 定义 Counter 组件
function Counter() {
const = useReducer(reducer, initialState);
return (
    &lt;div&gt;
      Count: {state.count}
      &lt;button onClick={() =&gt; dispatch({type: 'increment'})}&gt;+&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch({type: 'decrement'})}&gt;-&lt;/button&gt;
    &lt;/div&gt;
);
}

</code></pre>
<p>对于熟悉 redux 的伙伴,这种写法很容易就能理解,每次通过点击按钮分发一个action来更新state状态数据。</p>
<p>强调一点:</p>
<p>React 不使用 state = initialState 这一由 Redux 推广开来的参数约定。有时候初始值依赖于 props,因此需要在调用 Hook 时指定。如果你特别喜欢上述的参数约定,可以通过调用 useReducer(reducer, undefined, reducer) 来模拟 Redux 的行为,但我们不鼓励你这么做。</p>
<p>这里是 react 官网提供的一句话,也就是说我们为 state 提供初始值的时候不能够像 redux 中利用 ES6 默认参数来指定,必须得通过 useReducer来指定。这个切记,因为我也踩过坑!</p>
<p>对于 useReducer 和 useState的区别主要是以下两点:</p>
<ul>
<li>当 state 状态值结构比较复杂时,使用 useReducer 更有优势。</li>
<li>使用 useState 获取的 setState 方法更新数据时是异步的;而使用 useReducer 获取的 dispatch 方法更新数据是同步的。</li>
</ul>
<h2 id="性能优化hooksusememo">性能优化Hooks(useMemo)</h2>
<p>我们知道 使用class 形式的组件有着生命周期 shouldCompnentUpdate函数用来优化组件的性能,防止每次渲染造成昂贵的开销。<br>
使用function的形式来声明组件,失去了shouldCompnentUpdate(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。</p>
<p>useMemo主要用来解决使用React hooks产生的无用渲染的性能问题。</p>
<p>举个例子:</p>
<blockquote>
<p>在 A 组件中有两个子组件 B 和 C,当 A 组件中传给 B 的 props 发生变化时,A 组件状态会改变,重新渲染。此时 B 和 C 也都会重新渲染。其实这种情况是比较浪费资源的,现在我们就可以使用 useMemo 进行优化,B 组件用到的 props 变化时,只有 B 发生改变,而 C 却不会重新渲染。</p>
</blockquote>
<p>Example:</p>
<pre><code class="language-js">
// A 组件

import React from 'react';

export default ({ text }) =&gt; {
console.log('component A:', 'render');
return &lt;div&gt; A 组件:{ text }&lt;/div&gt;
}

// B 组件

import React from 'react';

export default ({ text }) =&gt; {
console.log('component B:', 'render');
return &lt;div&gt; B 组件:{ text }&lt;/div&gt;
}

// App 组件

import React, { useState, useMemo } from 'react';
import A from './ExampleA';
import B from './ExampleB';

export default () =&gt; {
const = useState('A');
const = useState('B');

const exampleA = useMemo(() =&gt; &lt;A text={a} /&gt;, );
const exampleB = useMemo(() =&gt; &lt;B text={b} /&gt;, );

return (
    &lt;div&gt;
      { exampleA }
      { exampleB }
      &lt;br /&gt;
      &lt;button onClick={ () =&gt; setA('A改变了') }&gt;修改传给 A 的属性&lt;/button&gt;
      &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
      &lt;button onClick={ () =&gt; setB('B改变了') }&gt;修改传给 B 的属性&lt;/button&gt;
    &lt;/div&gt;
)
}
</code></pre>
<p>我们点击不同的按钮,控制台都只会打印一条输出,改变 a 或者 b,A 和 B 组件都只有一个会重新渲染。</p>
<p>其实 React 还有很多常用的 Hooks,比如用来获取DOM元素 和保存变量的useRef,优化函数式组件性能的useCallback等,对于这些的Hooks大家可以去react 官网了解下,相信大家应该也能搞清楚,这里就不做进一步探讨。</p>
<p>React Hooks Api</p>
<h2 id="结语">结语</h2>
<p>本篇文章出自于我们 <code>web-study</code> 仓库,如果喜欢的话,欢迎给个 star!</p>
<blockquote>
<p>地址: web-study</p>
</blockquote><br><br>
来源:https://www.cnblogs.com/dreamcc/p/11529193.html
頁: [1]
查看完整版本: React Hooks 你不来了解下?