高文娣 發表於 2021-11-23 11:33:00

react之react Hooks

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>1、useState保存组件状态</li><li>2、useEffect 处理副作用</li><li>3、useContext 减少组件层级</li><li>4、useReducer 数据交互</li><li>5、useCallback 记忆函数</li><li>6、useMemo 记忆组件</li><li>7、useRef 保存引用值</li><li>8、useImperativeHandle 透传 Ref</li><li>9、useLayoutEffect 同步执行副作用</li></ul></div><br>
函数组件,没有 class 组件中的 componentDidMount、componentDidUpdate 等生命周期方法,也没有 State,但这些可以通过 React Hook 实现。<br>
<strong>React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来</strong><p></p>
<p><strong>React Hooks优点</strong></p>
<ol>
<li>代码可读性更强:通过 React Hooks 可以将功能代码聚合,方便阅读维护</li>
<li>组件树层级变浅:在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现</li>
</ol>
<p><strong>九种常用的钩子</strong></p>
<ol>
<li>useState:保存组件状态</li>
<li>useEffect: 处理副作用</li>
<li>useContext: 减少组件层级</li>
<li>useReducer:类似于redux,通信</li>
<li>useCallback: 记忆函数</li>
<li>useMemo: 记忆组件</li>
<li>useRef: 保存引用值</li>
<li>useImperativeHandle: 透传 Ref</li>
<li>useLayoutEffect: 同步执行副作用</li>
</ol>
<h2 id="1usestate保存组件状态">1、useState保存组件状态</h2>
<p><font color="red">用来代替:state,setState</font><br>
若使用对象做 State,useState 更新时会直接替换掉它的值,而不像 setState 一样把更新的字段合并进对象中。推荐将 State 对象分成多个 State 变量。<br>
<strong>类组件案例</strong><br>
在类组件中,我们使用 <code>this.state</code> 来保存组件状态,并对其修改触发组件重新渲染。</p>
<pre><code>import React from "react";
class App extends React.Component {
constructor(props) {
    super(props);
    this.state = {
      count: 0,
      name: "alife"
    };
}
render() {
    const { count } = this.state;
    return (
      &lt;div&gt;
      Count: {count}
      &lt;button onClick={() =&gt; this.setState({ count: count + 1 })}&gt;+&lt;/button&gt;
      &lt;button onClick={() =&gt; this.setState({ count: count - 1 })}&gt;-&lt;/button&gt;
      &lt;/div&gt;
    );
}
}
</code></pre>
<p><strong>函数组件useState</strong></p>
<p>在函数组件中,由于没有 this 这个黑魔法,React 通过 useState 来帮我们保存组件的状态。</p>
<ol>
<li>通过传入 useState 参数后返回一个带有默认状态和改变状态函数的数组 (名称是自定义的可修改...)。</li>
<li>通过传入 新状态给函数 来改变原本的状态值。</li>
</ol>
<pre><code>import React, { useState } from "react";
function App() {
const = useState({
    count: 0,
    name: "alife"
});
    let = useState(0)
return (
    &lt;div className="App"&gt;
      Count: {obj.count}
      &lt;button onClick={() =&gt; setObject({ ...obj, count: obj.count + 1 })}&gt;+&lt;/button&gt;
      &lt;button onClick={() =&gt; setObject({ ...obj, count: obj.count - 1 })}&gt;-&lt;/button&gt;
    &lt;/div&gt;
);
}
</code></pre>
<p><strong>注意: useState 不帮助你处理状态,相较于 setState 非覆盖式更新状态,useState 覆盖式更新状态,需要开发者自己处理逻辑。(代码如上)</strong></p>
<h2 id="2useeffect-处理副作用">2、useEffect 处理副作用</h2>
<p><font color="red">用来代替:componentDidMount、componentDidUpdate和componentWillUnmount的组合体。</font><br>
默认情况下,useEffect会在第一次渲染之后和每次更新之后执行,每次运行useEffect时,DOM 已经更新完毕。<br>
为了控制useEffect的执行时机与次数,可以使用第二个可选参数施加控制。<br>
<strong>类组件案例</strong><br>
在例子中,组件每隔一秒更新组件状态,并且每次触发更新都会触发 document.title 的更新(副作用),而在组件卸载时修改 document.title(类似于清除)</p>
<p>从例子中可以看到,一些重复的功能开发者需要在 componentDidMount 和 componentDidUpdate 重复编写,而如果使用 useEffect 则完全不一样。</p>
<pre><code>import React, { Component } from "react";
class App extends Component {
state = {
    count: 1
};

componentDidMount() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
    this.timer = setInterval(() =&gt; {
      this.setState(({ count }) =&gt; ({
      count: count + 1
      }));
    }, 1000);
}

componentDidUpdate() {
    const { count } = this.state;
    document.title = "componentDidMount" + count;
}

componentWillUnmount() {
    document.title = "componentWillUnmount";
    clearInterval(this.timer);
}

render() {
    const { count } = this.state;
    return (
      &lt;div&gt;
      Count:{count}
      &lt;button onClick={() =&gt; clearInterval(this.timer)}&gt;clear&lt;/button&gt;
      &lt;/div&gt;
    );
}
}
</code></pre>
<p><strong>useEffect</strong><br>
参数1:接收一个函数,可以用来做一些副作用比如异步请求,修改外部参数等行为。<br>
参数2:称之为dependencies,是一个数组,如果数组中的值变化才会触发 执行useEffect 第一个参数中的函数。返回值(如果有)则在组件销毁或者调用函数前调用</p>
<ul>
<li>
<ol>
<li>比如第一个 useEffect 中,理解起来就是一旦 count 值发生改变,则修改 documen.title 值;</li>
</ol>
</li>
<li>
<ol start="2">
<li>而第二个 useEffect 中传递了一个空数组[],这种情况下只有在组件初始化或销毁的时候才会触发,用来代替 componentDidMount 和 componentWillUnmount,慎用;</li>
</ol>
</li>
<li>
<ol>
<li>还有另外一个情况,就是不传递第二个参数,也就是useEffect只接收了第一个函数参数,代表不监听任何参数变化。每次渲染DOM之后,都会执行useEffect中的函数。</li>
</ol>
</li>
<li></li>
</ul>
<pre><code>import React, { useState, useEffect } from "react";
let timer = null;
function App() {
const = useState(0);

useEffect(() =&gt; {
    document.title = "componentDidMount" + count;
},);

useEffect(() =&gt; {
    timer = setInterval(() =&gt; {
      setCount(prevCount =&gt; prevCount + 1);
    }, 1000);
    // 一定注意下这个顺序:
    // 告诉react在下次重新渲染组件之后,同时是下次执行上面setInterval之前调用
    return () =&gt; {
      document.title = "componentWillUnmount";
      clearInterval(timer);
    };
}, []);

return (
    &lt;div&gt;
      Count: {count}
      &lt;button onClick={() =&gt; clearInterval(timer)}&gt;clear&lt;/button&gt;
    &lt;/div&gt;
);
}
</code></pre>
<h2 id="3usecontext-减少组件层级">3、useContext 减少组件层级</h2>
<p><font color="red">用来代替:Provider, Consumer</font><br>
处理多层级传递数据的方式,在以前组件树种,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props 往下透传之外,我们还可以使用 React Context API 来帮我们做这件事</p>
<p><strong>类组件案例</strong></p>
<pre><code>const { Provider, Consumer } = React.createContext(null);
function Bar() {
return &lt;Consumer&gt;{color =&gt; &lt;div&gt;{color}&lt;/div&gt;}&lt;/Consumer&gt;;
}

function Foo() {
return &lt;Bar /&gt;;
}

function App() {
return (
    &lt;Provider value={"grey"}&gt;
      &lt;Foo /&gt;
    &lt;/Provider&gt;
);
}
</code></pre>
<p><strong>useContext使用的方法</strong></p>
<ol>
<li>要先创建createContex<br>
使用createContext创建并初始化<pre><code>import{ createContext } from 'react'
const C = createContext(null);
</code></pre>
</li>
<li>Provider 指定使用的范围<br>
在圈定的范围内,传入读操作和写操作对象,然后可以使用上下文<pre><code>    &lt;C.Provider value={{n,setN}}&gt;
    这是爷爷
    &lt;Baba&gt;&lt;/Baba&gt;
    &lt;/C.Provider&gt;
</code></pre>
</li>
<li>最后使用useContext<br>
使用useContext接受上下文,因为传入的是对象,则接受的也应该是对象<pre><code>const {n,setN} = useContext(C);
</code></pre>
</li>
</ol>
<pre><code>import React, { createContext, useContext, useReducer, useState } from 'react'
import ReactDOM from 'react-dom'
// 创造一个上下文
const C = createContext(null);
function App(){
const = useState(0)
return(
    // 指定上下文使用范围,使用provider,并传入读数据和写入据
    &lt;C.Provider value={{n,setN}}&gt;
      这是爷爷
      &lt;Baba&gt;&lt;/Baba&gt;
    &lt;/C.Provider&gt;
)
}


function Baba(){
return(
    &lt;div&gt;
      这是爸爸
      &lt;Child&gt;&lt;/Child&gt;
    &lt;/div&gt;
)
}

function Child(){
// 使用上下文,因为传入的是对象,则接受也应该是对象
const {n,setN} = useContext(C)
const add=()=&gt;{
    setN(n=&gt;n+1)
};
return(
    &lt;div&gt;
      这是儿子:n:{n}
      &lt;button onClick={add}&gt;+1&lt;/button&gt;
    &lt;/div&gt;
)
}

ReactDOM.render(&lt;App /&gt;,document.getElementById('root'));

</code></pre>
<h2 id="4usereducer-数据交互">4、useReducer 数据交互</h2>
<p><font color="red">用来代替:Redux/React-Redux</font><br>
唯一缺少的就是无法使用 redux 提供的中间件</p>
<pre><code>import React, { useReducer } from "react";

const initialState = {
count: 0
};

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

function App() {
const = useReducer(reducer, initialState);
return (
    &lt;&gt;
      Count: {state.count}
      &lt;button onClick={() =&gt; dispatch({ type: "increment", payload: 5 })}&gt;
      +
      &lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch({ type: "decrement", payload: 5 })}&gt;
      -
      &lt;/button&gt;
    &lt;/&gt;
);
}
</code></pre>
<h2 id="5usecallback-记忆函数">5、useCallback 记忆函数</h2>
<p>减少重复渲染<br>
老规矩,第二个参数传入一个数组,数组中的每一项一旦值或者引用发生改变,useCallback 就会重新返回一个新的记忆函数提供给后面进行渲染。</p>
<p>这样只要子组件继承了 PureComponent 或者使用 React.memo 就可以有效避免不必要的 VDOM 渲染。</p>
<pre><code>import React, { useState, useCallback, memo } from 'react'

const Child = memo(function(props) {
console.log('child run...')
return (
    &lt;&gt;
      &lt;h1&gt;hello&lt;/h1&gt;
      &lt;button onClick={props.onAdd}&gt;add&lt;/button&gt;
    &lt;/&gt;
)
})

export default function UseCallback() {
console.log('parent run...')
let [ count, setCount ] = useState(0)

const handleAdd = useCallback(
    () =&gt; {
      console.log('added.')
    },
    [],
)
return (
    &lt;div&gt;
      &lt;div&gt;{count}&lt;/div&gt;
      &lt;Child onAdd={handleAdd}&gt;&lt;/Child&gt;
      &lt;button onClick={() =&gt; setCount(100)}&gt;change count&lt;/button&gt;
    &lt;/div&gt;
)
}
</code></pre>
<h2 id="6usememo-记忆组件">6、useMemo 记忆组件</h2>
<p>useCallback 的功能完全可以由 useMemo 所取代,如果你想通过使用 useMemo 返回一个记忆函数也是完全可以的。<br>
<strong>区别:</strong>useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你<br>
<code>    function App() {   const memoizedHandleClick = useMemo(() =&gt; () =&gt; {         console.log('Click happened')   }, []); // 空数组代表无论什么情况下该函数都不会发生改变   return &lt;SomeComponent onClick={memoizedHandleClick}&gt;Click Me&lt;/SomeComponent&gt;;   }    </code></p>
<ul>
<li>useCallback: 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。</li>
<li>useMemo: 更适合经过函数计算得到一个确定的值,比如记忆组件。<pre><code>function Parent({ a, b }) {
    const child1 = useMemo(() =&gt; () =&gt; &lt;Child1 a={a} /&gt;, );
    const child2 = useMemo(() =&gt; () =&gt; &lt;Child2 b={b} /&gt;, );
    return (
      &lt;&gt;
      {child1}
      {child2}
      &lt;/&gt;
    )
}
</code></pre>
</li>
</ul>
<h2 id="7useref-保存引用值">7、useRef 保存引用值</h2>
<p><font color="red">用来代替:createRef</font></p>
<p><strong>Ref</strong><br>
React提供了一个属性ref,用于表示对组价实例的引用,其实就是ReactDOM.render()返回的组件实例:</p>
<ul>
<li>ReactDOM.render()渲染组件时返回的是组件实例;</li>
<li>渲染dom元素时,返回是具体的dom节点。</li>
</ul>
<p>ref可以挂载到组件上也可以是dom元素上;</p>
<ul>
<li>挂到组件(class声明的组件)上的ref表示对组件实例的引用。不能在函数式组件上使用 ref 属性,因为它们没有实例:</li>
<li>挂载到dom元素上时表示具体的dom元素节点。</li>
</ul>
<p><strong>useRef()</strong><br>
useRef这个hooks函数,除了传统的用法之外,它还可以“跨渲染周期”保存数据。</p>
<ul>
<li>可以通过 ref.current 值访问组件或真实的 DOM 节点,从而可以对 DOM 进行一些操作,比如监听事件等等<pre><code>export default function App() {
      const count = useRef(0)
      &lt;!-- count.current存储状态值 --&gt;
      const handleClick = (num) =&gt; {
          count.current += num
          setTimeout(() =&gt; {
          console.log("count: " + count.current);
          }, 3000)
      }

      return (
          &lt;div&gt;
          &lt;p&gt;You clicked {count.current} times&lt;/p&gt;
          &lt;button onClick={() =&gt; handleClick(1)}&gt;增加 count&lt;/button&gt;
          &lt;button onClick={() =&gt; handleClick(-1)}&gt;减少 count&lt;/button&gt;
          &lt;/div&gt;
      );
}

</code></pre>
</li>
</ul>
<h2 id="8useimperativehandle-透传-ref">8、useImperativeHandle 透传 Ref</h2>
<p>通过 useImperativeHandle 用于让父组件获取子组件内的索引<br>
useImperativeHandle 应当与 forwardRef 一起使用<br>
<code>useImperativeHandle(ref, createHandle, )</code></p>
<ul>
<li>ref:定义 current 对象的 ref createHandle:一个函数,返回值是一个对象,即这个 ref 的 current</li>
<li>:即依赖列表,当监听的依赖发生变化,useImperativeHandle 才会重新将子组件的实例属性输出到父组件</li>
<li>注意:ref 的 current 属性上,如果为空数组,则不会重新输出。</li>
</ul>
<pre><code>import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";

function ChildInputComponent(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () =&gt; inputRef.current);
return &lt;input type="text" name="child input" ref={inputRef} /&gt;;
}

const ChildInput = forwardRef(ChildInputComponent);

function App() {
const inputRef = useRef(null);
useEffect(() =&gt; {
    inputRef.current.focus();
}, []);
return (
    &lt;div&gt;
      &lt;ChildInput ref={inputRef} /&gt;
    &lt;/div&gt;
);
}

export default App
</code></pre>
<h2 id="9uselayouteffect-同步执行副作用">9、useLayoutEffect 同步执行副作用</h2>
<p>大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。<br>
<strong>(1) useEffect和useLayoutEffect有什么区别?</strong></p>
<p>简单来说就是调用时机不同</p>
<ul>
<li><code>useLayoutEffect</code>:和原来<code>componentDidMount</code>&amp;<code>componentDidUpdate</code>一致,在react完成DOM更新后马上同步调用的代码,会阻塞页面渲染。</li>
<li><code>useEffect</code>:是会在整个页面渲染完才会调用的代码。</li>
<li>官方建议优先使用<code>useEffect</code></li>
</ul>
<p>在实际使用时如果想避免<strong>页面抖动</strong>(在<code>useEffect</code>里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在<code>useLayoutEffect</code>里。关于使用<code>useEffect</code>导致页面抖动。<br>
不过<code>useLayoutEffect</code>在服务端渲染时会出现一个warning,要消除的话得用<code>useEffect</code>代替或者推迟渲染时机。</p>
<pre><code>import React from 'react'
import { useState, useEffect } from 'react'

export default function (props) {
let = useState({count: 0})

function loadData() {
    return fetch('http://localhost:8080/api/movies/list')
      .then(response =&gt; response.json())
      .then(result =&gt; {
      return result
      })
}

useEffect(() =&gt; {
    console.log('effect')
}, )

useEffect(() =&gt; {
    console.log('mounted.')

    ;(async ()=&gt;{
      let result = await loadData()
      console.log(result)
    })()

    // return () =&gt; {
    //   console.log('unmout')
    // }
}, [])

return (
    &lt;&gt;
      &lt;div&gt;{data.count}&lt;/div&gt;
      &lt;button onClick={() =&gt; setData(data =&gt; ({count: data.count + 1}))}&gt;click&lt;/button&gt;
    &lt;/&gt;
)
}

</code></pre><br><br>
来源:https://www.cnblogs.com/ypSharing/p/15592458.html
頁: [1]
查看完整版本: react之react Hooks