1.引导
Hook是React16.8⼀一个新增项,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
2.Hooks的特点:
- 使你在无需修改组件结构的情况下复⽤状态逻辑
 - 可将组件中相互关联的部分拆分成更⼩的函数,复杂组件将变得更容易理解
 - 更简洁、更易理解的代码
 
3.useState
import React, { useState } from 'react'function App() {const [count, setCount] = useState(0) // ES6解构语法return (<div><h1>you click {count} times</h1><input type="button" onClick={()=> setCount(count + 1)} value="click me" /></div>)}export default App
每次点击按钮时,setCount 方法接收的 count 都是最新状态,之后 React 会重新渲染组件并保留新的状态。
如果一个组件需要多个状态,我们可以在组件中多次使用 useState
4.useEffect
useEffect 给函数组件增加了执行副作用操作的能力。
副作⽤(Side Effect)是指⼀个 function 做了和本身运算返回值⽆关的事,⽐如:修改了全局变量、修改了传⼊的参数、甚⾄是 console.log(),所以 ajax 操作,修改 dom 都是算作副作⽤。
import React, { useEffect, useState } from 'react'function App() {const [count, setCount] = useState(0)useEffect(() => {// 模拟发送请求信息,在一秒后改变 count 的值setTimeout(() => {setCount(10)}, 1000)})return (<div>{count}</div>)}export default App
测试会发现副作⽤操作会被频繁调⽤。
- 设置依赖,让只执行一次副作用,如果副作⽤操作对某状态有依赖,务必添加依赖选项。
 
// 设置空数组意为没有依赖,则副作⽤操作仅执行⼀一次useEffect(()=>{...}, [])
- 清除⼯作:有⼀些副作用是需要清除的,清除⼯作非常重要的, 可以防⽌引起内存泄露。
 
useEffect(() => {const timer = setInterval(() => {console.log('msg');}, 1000);return function(){clearInterval(timer);}}, []);
组件卸载后会执行返回的清理理函数。
5.useReducer
useReducer是useState的可选项,常⽤于组件有复杂状态逻辑时,类似于redux中reducer概念。
const initialState = {count: 0};function reducer(state, action) {switch (action.type) {case 'increment':return {count: state.count + 1};default:throw new Error()}}import React, { useReducer } from 'react'function App() {const [state, dispatch] = useReducer(reducer, initialState)return (<div><h1>you click {state.count} times</h1><input type="button" onClick={()=> dispatch({type: 'increment'})} value="click me" /></div>)}export default App
以下场景,你可以优先使用 useReducer :
state变化很复杂,经常一个操作需要修改很多state;- 深层子组件里去修改一些状态;
 - 应用程序比较大,UI 和业务需要分开维护。
 
6.useContext
context 做的事情就是创建一个上下文对象,并且对外暴露提供者和消费者,在上下文之内的所有子组件,都可以访问这个上下文环境之内的数据,并且不用通过 props。 简单来说, context 的作用就是对它所包含的组件树提供全局共享数据的一种技术。
import React, { useContext } from "react";const Context = React.createContext();const Provider = Context.Provider;export default function HookContext() {const store = { userName: "xiaoming" };return (<div><h1>HookContext ⻚页⾯面</h1><Provider value={store}><Child /></Provider></div>);}function Child(props) {const { userName } = useContext(Context);return (<div>Child<div>userName: {userName}</div></div>);}
7.useMemo & useCallback
useCallback 和 useMemo 的第一个参数是一个执行函数,第二个参数可用于定义其依赖的所有变量。如果其中一个变量发生变化,则 useMemo 或者 useCallback 会运行。这两个 API 都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行,并且这两个 Hooks 都返回缓存的值,useMemo 返回缓存的变量, useCallback 返回缓存的函数。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])const memoizedCallback = useCallback(() => {doSomething(a, b);},[a, b],)
1.useMemo
通过使用 useMemo,我们可以将上一次执行的结果缓存在变量中,只有在 count 值变化时进行更新执行。
function WithoutMemo() {const [count, setCount] = useState(1)const [val, setValue] = useState('')const expensive = useMemo(() => {console.log('compute')// 假设是个计算量比较大的函数return count * 100}, [count])return (<div><h4>{count}-{val}-{expensive}</h4><div><button onClick={() => setCount(count + 1)}>+c1</button><input value={val} onChange={event => setValue(event.target.value)} /></div></div>)}
2.useCallback
当我们父组件中包含一个子组件,子组件接收一个函数作为 props,通常的情况是,如果父组件更新,那么子组件也会随之更新。但我们知道,大多数情况下子组件的更新是没有必要的,这时我们可以借助 useCallback 来解决这个问题,通过 useCallback 返回的函数,然后把这个缓存的函数传递给子组件,这样只有当这个函数发生变化时,子组件才会更新。一起来看下例子:
// 子组件function SubComponent(props){console.log('SubComponent render');return (<button onClick={props.onClick}>{props.data.count}</button>)}// 父组件const SubComponent = memo(SubComponent);export default function WithMemo(){console.log('Counter render');const [val, setValue]= useState('计数器');const [count,setCount] = useState(0);const data = useMemo(()=>({count}),[count]);// 有没有后面的依赖项数组很重要,否则还是会重新渲染const addClick = useCallback(()=>{setCount(count+1);}, [count]);return (<><input value={val} onChange={event => setValue(event.target.value)} /><SubComponent data={data} onClick={addClick}/></>)}
8.useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的 ref 对象都是同一个。
function ChildComponent() {const inputEl = useRef(null)const curFocus = () => {// `current` 指向已挂载到 DOM 上的文本输入元素inputEl.current.focus()}return (<><input ref={inputEl} type="text" /><button onClick={curFocus}>Focus the input</button></>)}
9.useLayoutEffect
其函数签名与 useEffect 相同,useEffect 是在全部渲染完之后才会执行,而 useLayoutEffect 会在浏览器布局之后,绘制之前执行。useLayoutEffect 是在页面绘制之前执行,因此会阻塞视图更新。
function LayoutEffect() {const [color, setColor] = useState("red")useLayoutEffect(() => {alert(color)})useEffect(() => {console.log('color', color)})return (<><div id="myDiv" style={{ background: color }}>颜色</div><button onClick={() => setColor('red')}>红</button><button onClick={() => setColor('yellow')}>黄</button><button onClick={() => setColor('blue')}>蓝</button></>)
10.自定义hooks
Hooks 代码还可以封装起来,变成一个自定义的 Hook,便于共享。
const usePerson = (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]);return [loading, person];}const Person = ({ personId }) => {const [loading, person] = usePerson(personId);if (loading === true) {return <p>Loading ...</p>;}return (<div><p>You're viewing: {person.name}</p><p>Height: {person.height}</p><p>Mass: {person.mass}</p></div>);};
