常用React Hooks 方法

image.png useState 使用状态 const [n, setN] = React.useState(0) const [user, setUser] = React.useState({name: 'Jack', age: 18}) 注意事项1: 不可局部更新 如果state是一个对象,能否部分setState? 答案是不行,因为setState不会帮我们合并属性 那么useReducer会合并属性吗?也不会! 因为React认为这应该是你自己要做的事情 function App(){ const [user, setUser] = React.useState({name: 'Jack', age: 18}) const onClick = () =>{ //setUser不可以局部更新,如果只改变其中一个,那么整个数据都会被覆盖 // setUser({ // name: 'Frank' // }) setUser({ ...user, //拷贝之前的所有属性 name: 'Frank' //这里的name覆盖之前的name }) } return (

{user.name}

{user.age}

) } 注意事项2: 地址要变 setState(obj) 如果obj内存地址不变,那么React就认为数据没有变化,不会更新视图,了解内存地址与 桟堆概念 useState接受函数 const [state, setState] = useState(() => {return initialState}) 该函数返回初始state,且只执行一次 setState接受函数 setxxxx(i => i + 1) 如果你能接受这种形式,应该优先使用这种形式 修改之后的状态 会在函数组件 再次更新完之后 拿到最新的值,如果想立刻拿到最新的值,使用useRef useRef useRef这个hooks函数,除了传统的用法之外,它还可以“跨渲染周期”保存数据。 import React, { useState, useEffect, useMemo, useRef } from 'react'; export default function App(props){ const [count, setCount] = useState(0); const doubleCount = useMemo(() => { return 2 * count; }, [count]); const couterRef = useRef(); useEffect(() => { document.title = `The value is ${count}`; console.log(couterRef.current); }, [count]); return ( <> ); } 代码中用useRef创建了couterRef对象,并将其赋给了button的ref属性。这样,通过访问couterRef.current就可以访问到button对应的DOM对象。 然后再来看看它保存数据的用法。 在一个组件中有什么东西可以跨渲染周期,也就是在组件被多次渲染之后依旧不变的属性?第一个想到的应该是state。没错,一个组件的state可以在多次渲染之后依旧不变。但是,state的问题在于一旦修改了它就会造成组件的重新渲染。 那么这个时候就可以使用useRef来跨越渲染周期存储数据,而且对它修改也不会引起组件渲染。 import React, { useState, useEffect, useMemo, useRef } from 'react'; export default function App(props){ const [count, setCount] = useState(0); const doubleCount = useMemo(() => { return 2 * count; }, [count]); const timerID = useRef(); useEffect(() => { timerID.current = setInterval(()=>{ setCount(count => count + 1); }, 1000); }, []); useEffect(()=>{ if(count > 10){ clearInterval(timerID.current); } }); return ( <> ); } 在上面的例子中,我用ref对象的current属性来存储定时器的ID,这样便可以在多次渲染之后依旧保存定时器ID,从而能正常清除定时器。 useReducer():action 钩子 React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。 Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState。 useReducers()钩子用来引入 Reducer 功能。 const [state, dispatch] = useReducer(reducer, initialState); 上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。 下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。 const myReducer = (state, action) => { switch(action.type) { case('countUp'): return { ...state, count: state.count + 1 } default: return state; } } 组件代码如下。 function App() { const [state, dispatch] = useReducer(myReducer, { count: 0 }); return (

Count: {state.count}

); } 由于 Hooks 可以提供共享状态和 Reducer 函数,所以它在这些方面可以取代 Redux。但是,它没法提供中间件(middleware)和时间旅行(time travel),如果你需要这两个功能,还是要用 Redux。 useEffect():副作用钩子 useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。 useEffect()的用法如下。 useEffect(() => { // Async Action }, [dependencies]) 上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()。 下面看一个例子。 const Person = ({ personId }) => { const [loading, setLoading] = useState(true); const [person, setPerson] = useState({}); useEffect(() => { setLoading(true); fetch(`https://swapi.co/api/people/${personId}/`) .then(response => response.json()) .then(data => { setPerson(data); setLoading(false); }); }, [personId]) if (loading === true) { return

Loading ...

} return

You're viewing: {person.name}

Height: {person.height}

Mass: {person.mass}

} 上面代码中,每当组件参数personId发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。 useContext 如果需要在组件之间共享状态,可以使用useContext()。 现在有俩个组件Navbar和Messages,我们希望它们之间共享状态。
使用方法如下: 第一步在它们的父组件上使用React的Context API,在组件外部建立一个Context。 const TestContext = React.createContext({)}; 组件封装代码如下:
上面的代码中,TestContext.Provider提供了一个Context对象,这个对象是可以被子组件共享的。 Navbar组件的代码如下: const Navbar = () => { const { username } = useContext(TestContext); return (

{username}

) } 上面代码中,useContext()钩子函数用来引入Context对象,从中获取username属性。 Message组件的代码也类似: const Messages = () => { const { username } = useContext(TestContext); return (

1 message for {username}

) } 整体代码如下: import React, { useContext } from "react"; import ReactDOM from "react-dom"; const TestContext= React.createContext({}); const Navbar = () => { const { username } = useContext(TestContext) return (

{username}

) } const Messages = () => { const { username } = useContext(TestContext) return (

1 message for {username}

) } function App() { return (
); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement); useMemo 我们来看一个反例: import React from 'react'; export default function WithoutMemo() { const [count, setCount] = useState(1); const [val, setValue] = useState(''); function expensive() { console.log('compute'); let sum = 0; for (let i = 0; i < count * 100; i++) { sum += i; } return sum; } return

{count}-{val}-{expensive()}

setValue(event.target.value)}/>
; } 这里创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。我们可以看到:无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印);但是这里的昂贵计算只依赖于count的值,在val修改的时候,是没有必要再次计算的。在这种情况下,我们就可以使用useMemo,只在count的值修改时,执行expensive计算: export default function WithMemo() { const [count, setCount] = useState(1); const [val, setValue] = useState(''); const expensive = useMemo(() => { console.log('compute'); let sum = 0; for (let i = 0; i < count * 100; i++) { sum += i; } return sum; }, [count]); return

{count}-{expensive}

{val}
setValue(event.target.value)}/>
; } 上面我们可以看到,使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去。这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值。 useCallback useCallback跟useMemo比较类似,但它返回的是缓存的函数。我们看一下最简单的用法: const fnA = useCallback(fnB, [a]) import React, { useState, useCallback } from 'react'; const set = new Set(); export default function Callback() { const [count, setCount] = useState(1); const [val, setVal] = useState(''); const callback = useCallback(() => { console.log(count); }, [count]); set.add(callback); return

{count}

{set.size}

setVal(event.target.value)}/>
; } 我们可以看到,每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。 知道useCallback有什么样的特点,那有什么作用呢? 使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。 import React, { useState, useCallback, useEffect } from 'react'; function Parent() { const [count, setCount] = useState(1); const [val, setVal] = useState(''); const callback = useCallback(() => { return count; }, [count]); return

{count}

setVal(event.target.value)}/>
; } function Child({ callback }) { const [count, setCount] = useState(() => callback()); useEffect(() => { setCount(callback()); }, [callback]); return
{count}
} 不仅是上面的例子,所有依赖本地状态或props来创建函数,需要使用到缓存函数的地方,都是useCallback的应用场景。 多谈一点 useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用ref来访问。 参考文章: 阮一峰老师:http://www.ruanyifeng.com/blog/2019/09/react-hooks.html 优质博客: https://blog.csdn.net/sinat_17775997/article/details/94453167 https://www.jianshu.com/p/1252be39c702 https://www.jianshu.com/p/7e778adec7d1 https://www.jianshu.com/p/d6244228a427

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):