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>
);
};