Hook 是 React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期)。
Class组件存在的问题
Hook的出现,可以解决上面提到的这些问题;
简单总结一下hooks: 它可以让我们在不编写class的情况下使用state以及其他的React特性;但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决;
Hook的使用场景:
useState来自react,需要从react中导入,它是一个hook;
import React, { useState } from 'react' export default function ComplexHookState() { const [friends, setFrineds] = useState(["kobe", "lilei"]); const [students, setStudents] = useState([ { id: 110, name: "why", age: 18 }, { id: 111, name: "kobe", age: 30 }, { id: 112, name: "lilei", age: 25 }, ]) function addFriend() { friends.push("hmm"); setFrineds(friends); } function incrementAgeWithIndex(index) { const newStudents = [...students]; newStudents[index].age += 1; setStudents(newStudents); } return ( <div> <h2>好友列表:</h2> <ul> { friends.map((item, index) => { return <li key={index}>{item}</li> }) } </ul> <button onClick={e => setFrineds([...friends, "tom"])}>添加朋友</button> {/* 错误的做法 */} <button onClick={addFriend}>添加朋友</button> <h2>学生列表</h2> <ul> { students.map((item, index) => { return ( <li key={item.id}> <span>名字: {item.name} 年龄: {item.age}</span> <button onClick={e => incrementAgeWithIndex(index)}>age+1</button> </li> ) }) } </ul> </div> ) }
但是使用它们会有两个额外的规则:
State Hook的API就是 useState
目前我们已经通过hook在函数式组件中定义state,那么类似于生命周期这些呢?
useEffect的解析:
在class组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount中进行清除:比如我们之前的事件总线或Redux中手动调用subscribe;都需要在componentWillUnmount有对应的取消订阅;Effect Hook通过什么方式来模拟componentWillUnmount呢?
useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B:type EffectCallback = () => (void | (() => void | undefined));
为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数;如此可以将添加和移除订阅的逻辑放在一起;它们都属于 effect 的一部分;
import React, { useEffect, useState } from 'react' export default function EffectHookCancelDemo() { const [count, setCount] = useState(0); useEffect(() => { console.log("订阅一些事件"); return () => { console.log("取消订阅事件") } }, []); return ( <div> <h2>EffectHookCancelDemo</h2> <h2>{count}</h2> <button onClick={e => setCount(count + 1)}>+1</button> </div> ) }
使用多个Effect
Hook 允许我们按照代码的用途分离它们, 而不是像生命周期函数那样:React 将按照 effect 声明的顺序依次调用组件中的每一个 effect;
import React, { useState, useEffect } from 'react' export default function MultiEffectHookDemo() { const [count, setCount] = useState(0); const [isLogin, setIsLogin] = useState(true); useEffect(() => { console.log("修改DOM", count); }, [count]); useEffect(() => { console.log("订阅事件"); }, []); useEffect(() => { console.log("网络请求"); }, []); return ( <div> <h2>MultiEffectHookDemo</h2> <h2>{count}</h2> <button onClick={e => setCount(count + 1)}>+1</button> <h2>{isLogin ? "coderwhy": "未登录"}</h2> <button onClick={e => setIsLogin(!isLogin)}>登录/注销</button> </div> ) }
Effect性能优化
默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题:
我们如何决定useEffect在什么时候应该执行和什么时候不应该执行呢?useEffect实际上有两个参数:
但是,如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组 [],那么这里的两个回调函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了;
在之前的开发中,我们要在组件中使用共享的Context有两种方式:
但是多个Context共享时的方式会存在大量的嵌套:Context Hook允许我们通过Hook来直接获取某个Context的值;
很多人看到useReducer的第一反应应该是redux的某个替代品,其实并不是。
useReducer仅仅是useState的一种替代方案:
在某些场景下,如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分;或者这次修改的state需要依赖之前的state时,也可以使用;
import React, { useState, useReducer } from 'react'; import reducer from './reducer'; export default function Home() { // const [count, setCount] = useState(0); const [state, dispatch] = useReducer(reducer, {counter: 0}); return ( <div> <h2>Home当前计数: {state.counter}</h2> <button onClick={e => dispatch({type: "increment"})}>+1</button> <button onClick={e => dispatch({type: "decrement"})}>-1</button> </div> ) }
import React, { useReducer } from 'react'; import reducer from './reducer'; export default function Profile() { // const [count, setCount] = useState(0); const [state, dispatch] = useReducer(reducer, { counter: 0 }); return ( <div> <h2>Profile当前计数: {state.counter}</h2> <button onClick={e => dispatch({ type: "increment" })}>+1</button> <button onClick={e => dispatch({ type: "decrement" })}>-1</button> </div> ) }
export default function reducer(state, action) { switch(action.type) { case "increment": return {...state, counter: state.counter + 1}; case "decrement": return {...state, counter: state.counter - 1}; default: return state; } }
数据是不会共享的,它们只是使用了相同的counterReducer的函数而已。所以,useReducer只是useState的一种替代品,并不能替代Redux。
useCallback实际的目的是为了进行性能的优化。
如何进行性能的优化呢?
案例
通常使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存;
import React, { useState, useCallback, useMemo } from 'react' //不能进行的性能优化 export default function CallbackHookDemo01() { const [count, setCount] = useState(0); const increment1 = () => { console.log("执行increment1函数"); setCount(count + 1); } const increment2 = useCallback(() => { console.log("执行increment2函数"); setCount(count + 1); }, [count]); const increment3 = useMemo(() => { return () => { console.log("执行increment2函数"); setCount(count + 1); } }, [count]); return ( <div> <h2>CallbackHookDemo01: {count}</h2> <button onClick={increment1}>+1</button> <button onClick={increment2}>+1</button> </div> ) }
import React, {useState, useCallback, memo} from 'react'; /** * useCallback在什么时候使用? * 场景: 在将一个组件中的函数, 传递给子元素进行回调使用时, 使用useCallback对函数进行处理. */ const HYButton = memo((props) => { console.log("HYButton重新渲染: " + props.title); return <button onClick={props.increment}>HYButton +1</button> }); export default function CallbackHookDemo02() { console.log("CallbackHookDemo02重新渲染"); const [count, setCount] = useState(0); const [show, setShow] = useState(true); const increment1 = () => { console.log("执行increment1函数"); setCount(count + 1); } const increment2 = useCallback(() => { console.log("执行increment2函数"); setCount(count + 1); }, [count]); return ( <div> <h2>CallbackHookDemo01: {count}</h2> {/* <button onClick={increment1}>+1</button> <button onClick={increment2}>+1</button> */} <HYButton title="btn1" increment={increment1}/> <HYButton title="btn2" increment={increment2}/> <button onClick={e => setShow(!show)}>show切换</button> </div> ) }
useMemo实际的目的也是为了进行性能的优化。如何进行性能的优化呢?
案例:
import React, {useState, useMemo} from 'react'; function calcNumber(count) { console.log("calcNumber重新计算"); let total = 0; for (let i = 1; i <= count; i++) { total += i; } return total; } export default function MemoHookDemo01() { const [count, setCount] = useState(10); const [show, setShow] = useState(true); // const total = calcNumber(count); const total = useMemo(() => { return calcNumber(count); }, [count]); return ( <div> <h2>计算数字的和: {total}</h2> <button onClick={e => setCount(count + 1)}>+1</button> <button onClick={e => setShow(!show)}>show切换</button> </div> ) }
import React, { useState, memo, useMemo } from 'react'; const HYInfo = memo((props) => { console.log("HYInfo重新渲染"); return <h2>名字: {props.info.name} 年龄: {props.info.age}</h2> }); export default function MemoHookDemo02() { console.log("MemoHookDemo02重新渲染"); const [show, setShow] = useState(true); // const info = { name: "why", age: 18 }; const info = useMemo(() => { return { name: "why", age: 18 }; }, []); return ( <div> <HYInfo info={info} /> <button onClick={e => setShow(!show)}>show切换</button> </div> ) }