蔺德华 發表於 2022-3-9 12:20:00

React Hooks -- useRef/useContext/useMemo/useCallback/自定义Hooks

<p>  <strong>useRef</strong></p>
<p>  useRef()返回一个具有current属性的对象,称为ref对象。把对象赋值给原生的React Element元素的ref属性,就能获取到对应的真实的DOM元素。</p>
<div class="cnblogs_code">
<pre>import React, { useRef } from "react"<span style="color: rgba(0, 0, 0, 1)">;

const CustomTextInput </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const textInput </span>= useRef(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建ref对象</span>
const focusTextInput = () =&gt; textInput.current.focus(); <span style="color: rgba(0, 128, 0, 1)">// 组件挂载完成,</span><span style="color: rgba(0, 128, 0, 1)">ref对象的current属性指向真实的DOM(input元素),调用focus方法</span>

<span style="color: rgba(0, 0, 255, 1)">return</span> (&lt;&gt;
      &lt;input type="text" ref={textInput} /&gt;
      &lt;button onClick={focusTextInput}&gt;Focus the text input&lt;/button&gt;
    &lt;/&gt;);
}</pre>
</div>
<p>  把ref对象赋值给组件类型的React Element的ref属性时,也能获取到子组件,调用子组件中的方法,不过要配合使用forwardref和useImperativeHandle。forwardref把函数子组件包起来,组件就多接收了一个ref参数,把ref和要暴露出来的方法传递给useImperativeHandle。使用create-react-app&nbsp;创建React项目,在src中创建一个Counter组件</p>
<div class="cnblogs_code">
<pre>import React, { forwardRef, useImperativeHandle } from 'react'<span style="color: rgba(0, 0, 0, 1)">;
import { useState } from </span>"react"

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 组件被forwardRef之后,组件多接受一个ref参数</span>
const Counter = (props, ref) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const </span>= useState(0<span style="color: rgba(0, 0, 0, 1)">);
const clickHandler </span>= () =&gt; {setCount(c =&gt; c + 1<span style="color: rgba(0, 0, 0, 1)">);}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第一个参数就是ref,第二个参数是函数,返回一个对象,父组件中ref对象的current属性就指向这个对象。</span>
useImperativeHandle(ref, () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> ({
      click: clickHandler
    })
})<br>
</span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;p&gt;count is {count} &lt;/p&gt;
<span style="color: rgba(0, 0, 0, 1)">}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> forwardRef(Counter); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> export forwardRef(组件)</span></pre>
</div>
<p>  App中使用ref,获取的子组件暴露出来的对象,并在button的click回调函数中使用</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> App() {
    const counteRef </span>=<span style="color: rgba(0, 0, 0, 1)"> useRef();
    const handleClick </span>= () =&gt; {counteRef.current.click();} <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ref对象获取到子组件暴露出来的对象。</span>

    <span style="color: rgba(0, 0, 255, 1)">return</span> (&lt;React.Fragment&gt;
            &lt;Counter ref={counteRef}&gt;&lt;/Counter&gt; //<span style="color: rgba(0, 0, 0, 1)"> ref对象赋值给子组件的ref属性。
            </span>&lt;button onClick={handleClick}&gt;Add&lt;/button&gt;
      &lt;/React.Fragment&gt;);
}</pre>
</div>
<p>  如果项目中使用了Redux和React-Redux,connect中配置forwardRef: true&nbsp;</p>
<div class="cnblogs_code">
<pre>connect(null, null, null, { forwardRef: true })(组件);</pre>
</div>
<p>   ref对象是一个对象,属性可以保存任何值,useRef()可以接受一个参数,给属性赋初始值。有一个值在组件中,但它又与组件渲染无关,不是状态,不是属性,也不在JSX中使用,比如setTimeout返回的ID,这种值就可以放到ref中。更改ref的值,不会引起组件的重新渲染,因为值与渲染无关,并且在组件的整个生命周期中,ref对象一直存在。组件挂载,ref对象创建,组件销毁,ref对象销毁。</p>
<div class="cnblogs_code">
<pre>import React, { useRef, useEffect } from "react"<span style="color: rgba(0, 0, 0, 1)">;

const Timer </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const intervalRef </span>=<span style="color: rgba(0, 0, 0, 1)"> useRef();

useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    const id </span>= setInterval(() =&gt; {console.log("A second has passed");}, 1000<span style="color: rgba(0, 0, 0, 1)">);
    intervalRef.current </span>=<span style="color: rgba(0, 0, 0, 1)"> id;
   
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> () =&gt;<span style="color: rgba(0, 0, 0, 1)"> clearInterval(intervalRef.current);
});

const handleCancel </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> clearInterval(intervalRef.current);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> (&lt;&gt; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">...&lt;/&gt;);</span>
}</pre>
</div>
<p>  需要注意的是更新ref对象的值,是一个副作用,因为它脱离React渲染过程,所以要放到useEffect或useLayoutEffect中,或放到事件处理函数中。</p>
<div class="cnblogs_code">
<pre>import React, { useRef } from "react"<span style="color: rgba(0, 0, 0, 1)">;

const RenderCounter </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
const counter </span>= useRef(0<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> counter.current = counter.current + 1; 不建议直接写在这里</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 建议放到useEffect中</span>
useEffect(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    counter.current </span>= counter.current + 1<span style="color: rgba(0, 0, 0, 1)">;
});

</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;h1&gt;{`The component has been re-rendered ${counter} times`}&lt;/h1&gt;
<span style="color: rgba(0, 0, 0, 1)">);
};</span></pre>
</div>
<p>  <strong>useContext</strong></p>
<p>  React Context使组件之间可以共享数据,但不用从父组件层层传递到子组件。Context提供了一个上下文(环境),只要在这个上下文中,子组件可以直接获取。Context.Provider包含起来的组件,就为组件提供了上下文,它的value(共享数据)就是可供子组件直接获取的数据(使用useContext获取)。React.createContext()创建一个context,它接受一个可选的参数,就是共享数据的默认值。比如登录之后,用户信息,页面的其他地方都要获取到,把用户信息放到Context中。create-react-app react-context 创建项目,userContext.js 创建context对象</p>
<div class="cnblogs_code">
<pre>import React from 'react'<span style="color: rgba(0, 0, 0, 1)">;
export const UserContext </span>=<span style="color: rgba(0, 0, 0, 1)"> React.createContext(</span><span style="color: rgba(0, 0, 0, 1)">)</span></pre>
</div>
<p>  App.js 中,Header组件用于获取用户信息,Detail用于显示信息,要设一个user状态和改变user的setUser,让这两个数据共享,所以把它们用Context包起来。</p>
<div class="cnblogs_code">
<pre>import React, {useState} from "react"<span style="color: rgba(0, 0, 0, 1)">;
import Header from </span>"./Header"<span style="color: rgba(0, 0, 0, 1)">;
import Details from </span>'./Details'<span style="color: rgba(0, 0, 0, 1)">;
import { UserContext } from </span>'./userContext'<span style="color: rgba(0, 0, 0, 1)">;

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> App() {
    const </span>=<span style="color: rgba(0, 0, 0, 1)"> useState({});
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;UserContext.Provider value={{ user, setUser }}&gt;
            &lt;Header&gt;&lt;/Header&gt;
            &lt;Details&gt;&lt;/Details&gt;
      &lt;/UserContext.Provider&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}</span></pre>
</div>
<p>  Header和Detail都消费context中的数据,</p>
<div class="cnblogs_code">
<pre>import React, { useEffect, useContext } from "react"<span style="color: rgba(0, 0, 0, 1)">;
import { UserContext } from </span>'./userContext'<span style="color: rgba(0, 0, 0, 1)">;

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Header() {
    const { user, setUser } </span>=<span style="color: rgba(0, 0, 0, 1)"> useContext(UserContext);
    useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      fetch(</span>'https://jsonplaceholder.typicode.com/users/1'<span style="color: rgba(0, 0, 0, 1)">)
            .then(response </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> response.json())
            .then(user </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> setUser(user))
    })
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;div&gt;用户名:{user.name}&lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">}

export </span><span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Details() {
    const { user } </span>=<span style="color: rgba(0, 0, 0, 1)"> useContext(UserContext);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> (&lt;div&gt;详细信息: {`${user.email} ${user.phone}`}&lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    );
}</span></pre>
</div>
<p>  这有一个问题,Header中setUser时,App所有子组件都会重新渲染,能不能只渲染consumer组件?要使用children属性,children是一个特殊的属性,&lt;A&gt;&lt;B/&gt; &lt;/A&gt;,A组件包起来的B组件是A的children属性,它和A组件是同一个父组件。A组件怎么发生变化,B组件都不会变化。实现一个A组件,接受children属性,把要获取数据的子组件作为children传递给它。UserContext.js 添加</p>
<div class="cnblogs_code">
<pre>export const UserProvider = ({ children }) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    const </span>= useState(''<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;UserContext.Provider value={{ user, setUser }}&gt;<span style="color: rgba(0, 0, 0, 1)">
      {children}
    </span>&lt;/UserContext.Provider&gt;
}</pre>
</div>
<p>  App.js</p>
<div class="cnblogs_code">
<pre>import { UserProvider } from './userContext'<span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> App() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;UserProvider&gt;
            &lt;Header&gt;&lt;/Header&gt;
            &lt;Details&gt;&lt;/Details&gt;
      &lt;/UserProvider&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}</span></pre>
</div>
<p>  在Header中调用setUser时,UserProvider组件会重新渲染,但UserProvider的children不会重新渲染,因为children是从props中获取的属性,没有改变。只有消费context的组件(children)才会重新渲染。context的consumer,当Provider的值改变时,consumer 重新渲染。Any components that consume&nbsp;the context, however, do re-render in response to the change of value on the provider,&nbsp;not because the whole tree of components has re-rendered.</p>
<p>  随着共享数据越来越多,theme, user等,如果都写到一个value,如果value中有一个值改变,所有consumer都会重新渲染,没有必要。可以使用多个provider</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> AppProvider({ children }) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可能存在状态</span>
    <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;ThemeContext.Provider value="lava"&gt;
            &lt;UserContext.Provider value="sam"&gt;
                &lt;LanguageContext.Provider value="en"&gt;<span style="color: rgba(0, 0, 0, 1)">
                  {children}
                </span>&lt;/LanguageContext.Provider&gt;
            &lt;/UserContext.Provider&gt;
      &lt;/ThemeContext.Provider &gt;
<span style="color: rgba(0, 0, 0, 1)">    );
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用它</span>
&lt;AppProvider&gt;
    &lt;App/&gt;
&lt;/AppProvider&gt;</pre>
</div>
<p>  子组件,可以只想获取自己想要的值。</p>
<div class="cnblogs_code">
<pre>function InfoPage(props) {
    const theme = useContext(ThemeContext);
    const language = useContext(LanguageContext);
    return (/* UI */);
}

function Messages(props) {
    const theme = useContext(ThemeContext);
    const user = useContext(UserContext);
    // subscribe to messages for user
    return (/* UI */);
}</pre>
</div>
<p>  组件之间简单共享数据另一种方式是Render props:就是属性可以被渲染(render)。属性可以是组件了,把整个组件作为属性进行传递,或者,属性是个函数,返回一个被渲染的组件。当属性是函数时,可以传一个data,渲染返回的组件时使用。比如Button组件有一个图标,为了能够更改图标,可能要提供size,width,color等参数,Button组件接受的参数越来越多,Button和Icon强绑定到一起,为什么不让图标整个组件传递过来?Button不管什么图标,给了,就渲染到合适的位置就行。</p>
<div class="cnblogs_code">
<pre>export function List({ data =<span> [], renderItem, renderEmpty }) {
    return data.length === 0
      ?<span> (renderEmpty)
      : (
            &lt;ul&gt;<span>
                {data.map((item, i) =&gt;<span> (
                  &lt;li key={i}&gt;{renderItem(item)}&lt;/li&gt;<span>
                ))}
            &lt;/ul&gt;<span>
      );
}</span></span></span></span></span></span></pre>
</div>
<p>  使用组件</p>
<div class="cnblogs_code">
<pre>&lt;<span>List
data={[{name: 'Rose', elevation: 2<span>}]}
renderEmpty={&lt;p&gt;This list is empty&lt;/p&gt;<span>}
renderItem={item =&gt;<span> <span>{item.name} -<span> {item.elevation.toLocaleString()}ft<span>}
/&gt;</span></span></span></span></span></span></span></pre>
</div>
<p>  <strong>useMemo/useCallback</strong></p>
<p>  React函数组件的最大问题就是,每次更新,组件就会重新渲染。里面的值就会重新计算,函数就会重新创建。函数通常会作为参数传递给子组件,又会引起子组件的重新渲染,导致没有必要的渲染。如果值不想重新计算,就要useMemo,记住函数的返回值。函数不想重新创建,就用useCallback包起来,它不会生成新函数,返回原函数。当然,它们都接受第二个参数,依赖数组,指定它们接受的函数的依赖,如果依赖有变化,值会重新计算并记住,函数会重新生成。子组件再配合使用React.memo,组件被调用的时候有相同的属性,子组件不会重复渲染。React.memo也可以单独使用,当一个组件渲染的时候,它的子组件也会渲染,不管子组件有没有变化,使用React.memo,子组件不会重新渲染。当把对象或数组作属性传递给子组件时,传递过去的是引用,所以才要求setState返回一个全新的对象,要不然传递过去的都是同一个引用,子组件不会更新。</p>
<div class="cnblogs_code">
<pre>import { useState } from "react"<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Items({ items }) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;&gt;
      &lt;h2&gt;Todo items&lt;/h2&gt;
      &lt;ul&gt;<span style="color: rgba(0, 0, 0, 1)">
      {items.map((todo) </span>=&gt; &lt;li key={todo}&gt;{todo}&lt;/li&gt;)}
      &lt;/ul&gt;
    &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Todo() {
const </span>= useState(["Clean gutter", "Do dishes"<span style="color: rgba(0, 0, 0, 1)">]);
const </span>= useState(""<span style="color: rgba(0, 0, 0, 1)">);
const onSubmit </span>= (evt) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    setItems((items) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> items.concat());
    setNewItem(</span>""<span style="color: rgba(0, 0, 0, 1)">);
    evt.preventDefault();
};
const onChange </span>= (evt) =&gt;<span style="color: rgba(0, 0, 0, 1)"> setNewItem(evt.target.value);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
    </span>&lt;main&gt;
      &lt;Items items={items} /&gt;
      &lt;form onSubmit={onSubmit}&gt;
      &lt;input value={newItem} onChange={onChange} /&gt;
      &lt;button&gt;Add&lt;/button&gt;
      &lt;/form&gt;
    &lt;/main&gt;
<span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 0, 255, 1)">function</span> App() { <span style="color: rgba(0, 0, 255, 1)">return</span> &lt;Todo /&gt;;}
export <span style="color: rgba(0, 0, 255, 1)">default</span> App;</pre>
</div>
<p>  对于Items组件来说,items属性是引用。在输入框中输入数据,Items组件也会被渲染,只是因为父组件渲染,子组件一定会渲染,而不是因为items属性发生变化,此时Items引用并没有发生变化。只有点击Add按钮,setState返回了一个全新的数组,items属性指向了另外一个引用,这时才是因为属性发生了变化,子组件会重新渲染。输入时,items子组件是没有必要重复渲染的,所以可以用React.memo 包起来。但是当在Jsx中内联一个数组时,&lt;Items items={['Complete to do list', ...items]},输入数据,父组件每次渲染都会重新创建一个数组,React.momeo 就不起作用了,这时用useMemo。</p>
<div class="cnblogs_code">
<pre><span>function Todo() {
// ...
const allItems = useMemo(() =&gt; ["Complete todo list"<span>], )
return<span> (
    &lt;main&gt;
      &lt;Items items={allItems} /&gt;
       // ...   
    &lt;/main&gt;<span>
);
}</span></span></span></span></pre>
</div>
<p>  In fact, useMemo doesn't do anything more than an assignment, except the assignment is conditional。如果Items 组件要删除,它需要接受函数参数,那就要把函数memorize.</p>
<div class="cnblogs_code">
<pre>const onDelete = useCallback((item) =&gt;<span> {
    setItems((list) =&gt; list.filter(i =&gt; i !==<span> item))
}, [])

&lt;Items items={allItems} onDelete ={onDelete} /&gt;</span></span></pre>
</div>
<p>  useMemo和useCallback只是返回一个值或函数,它和React的更新机制没有关系。</p>
<div class="cnblogs_code">
<pre>const Title = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
&nbsp;&nbsp;const a </span>= useMemo(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> { ... }, [])
&nbsp;&nbsp;</span><span style="color: rgba(0, 0, 255, 1)">return</span> &lt;Child a={a} /&gt;
}</pre>
</div>
<p>  Title组件更新,Child组件一定更新,不管a的值有没有变化。</p>
<p>  <strong>自定义hooks</strong></p>
<p>  使用hooks的函数组件鼓励你把相关副作用的逻辑放到一个地方。如果副作用是一个许多组件都需要的功能,可以把这段副作用的代码逻辑抽取出来,形成一个函数,这个函数就是自定义hook。比如做调查问卷的问题组件,都要加载问题,用户都要订阅,可以他们抽取到自定义hook中。和这些副作用相关的state,也要同步移动到相应的hook 中(Any state that is used solely for those 副作用 can be moved itno the corresponding hook)。</p>
<p><img src="https://img2024.cnblogs.com/blog/1013082/202409/1013082-20240903220030812-991005260.png"></p>
<p>  自定义hook可以维护自己的状态,它需要state来完成它的功能。由于hook 仅仅是函数,如果其他组件需要访问任意hook的state,hook可以把state 进行返回,包含到它的返回值中。比如一个自定义hook根据userId获取用户信息,它可以把获取到的用户数据store it locally,然后把它返回给调用hook的组件。每一个hook 封装自己的状态,就像其他函数一样。</p>
<div class="cnblogs_code">
<pre>const useToggle = (initialStatus = <span style="color: rgba(0, 0, 255, 1)">false</span>) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    const </span>=<span style="color: rgba(0, 0, 0, 1)"> useState(initialStatus)

    const toggle </span>= () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      setStatus(status </span>=&gt; !<span style="color: rgba(0, 0, 0, 1)">status)
    }

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<p>  简单自定义fetch hook</p>
<div class="cnblogs_code">
<pre>import React, { useState, useEffect } from "react"<span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> useFetch(uri){
    const </span>=<span style="color: rgba(0, 0, 0, 1)"> useState();
    const </span>=<span style="color: rgba(0, 0, 0, 1)"> useState();
    const </span>= useState(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);

    useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!uri) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      fetch(uri).then(data </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> data.json())
            .then(setData)
            .then(() </span>=&gt; setLoading(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">))
            .</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)">(setError);
    }, );

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { loading, data, error };
}</span></pre>
</div>
<p>  在useFetch的基础上,如果一个应用中,loading和error的处理方式都一样,完全可以封装出一个fetch组件出来。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Fetch({ url, renderSucess,
    loadingFallback </span>= &lt;p&gt;loading&lt;/p&gt;,
    renderError = error =&gt; {&lt;pre&gt;{JSON.stringify(error)}&lt;/pre&gt; }
<span style="color: rgba(0, 0, 0, 1)">}) {
    const {loading, data, error} </span>=<span style="color: rgba(0, 0, 0, 1)"> useFetch(url);

    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(loading) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> loadingFallback;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (error) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> renderError(error);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (data) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> renderSucess({data});
}</span></pre>
</div>
<p>  自定义hooks,当前组件是否已经挂载</p>
<div class="cnblogs_code">
<pre>export <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> useMountedRef() {
const mounted </span>= useRef(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
useEffect(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    mounted.current </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> () =&gt; (mounted.current = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
});
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> mounted;
}</span></pre>
</div>
<p>  当使用自定义hooks时,hooks内状态发生变化,使用hooks的组件会重新渲染(if the hook's state changes, the "host" component will re-render 相当于<strong>lifted state up</strong>),使用自定义hooks的组件一定小,只重复渲染它自己(把自定义hooks包装到小的组件中)。当在一个自定义hooks中,使用另一个hooks,hooks中状态变化会冒泡到包含最外层自定义hooks的组件。chaining hooks: if a hook’s state changes, it will cause its “host” hook change as well, which will propagate up through the whole chain of hooks until it reaches the “host” component and re-renders it (which will cause another chain reaction of re-renders, only downstream now)。一个自定义hooks,一个state,不相干的state放到不同的自定义hooks中,一个自定义hooks做一件事。当有了自定义hooks,useEffect的执行顺序是子组件的useEffect先执行,父组件的自定义hooks中的useEffect后执行,最后才是父组件本身的useEffect执行。</p>
<p>&nbsp;</p>
<p>  自定义hooks也是函数,只不过使用useState和useEffect等基本hook,把带有状态的公共逻辑抽离出来,一个自定义hooks可以使用另外一个自定义hooks,react的更新是组件级更新,最小的更新的单元是组件,只要一个hooks引起状态改变,最顶层hooks的所在组件,就会更新,导致所有子组件更新。</p>
<p>  </p><br><br>
来源:https://www.cnblogs.com/SamWeb/p/15976977.html
頁: [1]
查看完整版本: React Hooks -- useRef/useContext/useMemo/useCallback/自定义Hooks