useState
useState接收一个初始值,返回的是一个解构出来的数组,第一个是当前状态(似state),第二个是状态的更新函数(似setState),更新函数与react的setState不同的是,useState的更新函数会将状态替换(replace)而不是合并(merge)。
使用场景:函数组件中需要用到状态值,类似react类组件的state
import React, {
useState } from 'react';function Example() {
// 声明一个新的叫做 “count” 的 state 变量,0是默认值const [count, setCount] = useState(0);return (<div><p>You clicked {
count} times</p><button onClick={
() => setCount(count + 1)}>Click me</button></div>);
}
export default Example;
默认值:useState()括号后面,可传入值,也可以传函数,该函数只会渲染一遍
const [ count, setCount ] = useState(() => {
return props.count || 0
})
更新渲染:当我们在使用 useState 更新值时,组件重新渲染。若传入的更新值时为不变时,组件不会重新渲染。
useReducer
使用场景:由于useState的更新函数采用的是替换的方式,当我们要在函数组件中处理复杂状态时,如对象和数组等等,使用useState就不尽人意了。因此,我们使用useReducer来解决这一问题
const [state, dispatch] = useReducer(reducer, initialArg, init);
它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法,initialArg为初始值。
import React, {
useReducer } from 'react';
// 修改一个状态对象中的某属性,如果用useState,则要重新赋值一个对象,每个属性名都要写一遍
const initialState = {
count: 0, name: '名字', age: 1};function reducer(state, action) {
switch (action.type) {
case 'increment':return {
...state, count: state.count + 1};case 'decrement':return {
...state, count: state.count - 1};default:throw new Error();}
}function Example() {
const [state, dispatch] = useReducer(reducer, initialState);return (<>Count: {
state.count}<button onClick={
() => dispatch({
type: 'decrement'})}>-</button><button onClick={
() => dispatch({
type: 'increment'})}>+</button></>);
}
export default Example;
init是个函数,并以initialArg为入参,用于操作initialArg,返回自定义的初始值。
import React, {
useReducer } from 'react';
function init(initialCount) {
return {
count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':return {
count: state.count + 1};case 'decrement':return {
count: state.count - 1};case 'reset':return init(action.payload);default:throw new Error();}
}
function Example({
initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);return (<>Count: {
state.count}<buttononClick={
() => dispatch({
type: 'reset', payload: initialCount})}>Reset</button><button onClick={
() => dispatch({
type: 'decrement'})}>-</button><button onClick={
() => dispatch({
type: 'increment'})}>+</button></>);
}
export default Example;
useEffect
使用场景:useEffect副作用,使函数组件拥有了类似react的声明周期。useEffect会在组件每次render之后调用,useEffect有两个参数,第一个为执行函数,第二个为数组[]
如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合
import React, {
useState, useEffect } from 'react';function Example() {
const [count, setCount] = useState(0);const [dataSources, setDataSources] = useState([]);/* * 情况一:useEffect无第二个参数 *///组件初始化和render后,都会执行该useEffectuseEffect(() => {
console.log("相当于生命周期:componentDidMount+componentDidUpdate")});/* * 情况二:useEffect有第二个参数 *///第二个参数为空数组时:组件初始化才执行useEffect(() => {
console.log("相当于生命周期:componentDidMount"); }, []);//第二个参数为指定状态值时:组件初始化时和dataSources发生变化才执行useEffect(() => {
console.log("相当于生命周期:componentDidMount")console.log("相当于依赖dataSources状态值的生命周期:componentDidUpdate")}, [dataSources]);//执行函数内return一个函数:初始化时执行函数体,组件卸载unmount时执行return后的函数useEffect(() => {
console.log("相当于生命周期:componentDidMount")// 执行函数中直接使用return返回一个函数,这个函数会在组件unmount时执行。return () => {
console.log('相当于声明周期:componentWillUnmount'); }}, []);return (<div><p>You clicked {
count} times</p><button onClick={
() => setCount(count + 1)}>Click me</button></div>);
}
export default Example;
官方提示:与 componentDidMount 或 componentDidUpdate 不同,useEffect是异步的,使用
useEffect 调度的 effect
不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。官方建议尽可能使用useEffect,effect 不需要同步地执行
在个别情况下(例如测量布局,页面状态值闪烁bug时),才使用useLayoutEffect代替useEffect, 形成同步,在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制
React.memo和useCallback和useMemo
React.memo
使用场景:React.memo就类似React.PureComponent,专门用于纯函数组件,起作用是对内部对象进行浅比较,判断是否重新渲染。
import React, {
useState, useCallback } from 'react';
function Example() {
const [count, setCount] = useState(0);const [list, setList] = useState([]);return (<div><p>You clicked {
count} times</p><button onClick={
() => setCount(count + 1)}>addCount</button><button onClick={
() => setList([...list, 1])}>changeList</button><Child list={
list} /></div>);
}
const Child = (props) => {
console.log("进入了组件child")return (<div>这里是child:list为{
props.list.join(',')}</div>)
}
export default Example;
此时,无论是改变count状态值,还是name状态值,Child组件都会重新渲染,如果我们想让Child组件在其props传入的值,即list改变时才渲染
用React.memo解决,修改Child如下:
const Child = React.memo((props) => {
console.log("进入了组件child")return (<div>这里是child:{
props.list.join(',')}</div>)
})
useCallback
使用场景:useCallback用来缓存方法,类似react组件构造函数里面定义的:this.onChange =
this.onChange.bind(this),返回一个缓存的函数
上面例子还存在一个问题,如果Child组件传入一个函数作为参数,由于函数为引用类型,使得每次传入Child组件都是一个新的函数实例,如下
import React, {
useState, useCallback } from 'react';
function Example() {
const [count, setCount] = useState(0);const [list, setList] = useState([]);const handleChange = () => {
console.log(`selected`);}return (<div><p>You clicked {
count} times</p><button onClick={
() => setCount(count + 1)}>addCount</button><button onClick={
() => setList([...list, 1])}>changeList</button><Child list={
list} handleChange={
handleChange} /></div>);
}
const Child = React.memo((props) => {
console.log("进入了组件child")return (<div>这里是child:list为{
props.list.join(',')}</div>)
})
export default Example;
此时,无论是改变count状态值,还是list状态值,Child组件都会重新渲染
用useCallback解决,修改handleChange方法如下:
// 第二个参数为空数组,也可以指定依赖于某一状态值,即该状态值变化时才重新改变handleChange方法
const handleChange = useCallback(() => {
console.log(`selected`);
},[])// 或[list]
useMemo
使用场景:useMemo函数用于缓存需计算操作的状态值,类似Vue的计算属性。第一个参数为计算函数,且必须return返回一个结果,返回一个缓存的值,第二个参数是一个数组[],useMemo执行依赖于次数组的状态值
import React, {
useState, useCallback, useMemo } from 'react';
function Example() {
const [count, setCount] = useState(0);const [list, setList] = useState([]);const [a, setA] = useState(0)const [b, setB] = useState(0)const handleChange = useCallback(() => {
console.log(`selected`);},[])//使用useMemo缓存一个计算值,计算函数的执行依赖于状态值a和b,当a和b变化时才执行计算函数const memoizedValue = useMemo(() => a + b, [a, b]);return (<div><p>You clicked {
count} times</p><button onClick={
() => setCount(count + 1)}>addCount</button><button onClick={
() => setList([...list, 1])}>changeList</button><button onClick={
() => {
setA(a+1)}}>点我A</button><button onClick={
() => {
setB(b+1)}}>点我B</button><Child list={
list} memoizedValue={
memoizedValue} handleChange={
handleChange} /></div>);
}
const Child = React.memo((props) => {
console.log("进入了组件child")return (<div>这里是child:list为{
props.list.join(',')}, 计算总和:{
props.memoizedValue}</div>)
})
export default Example;
useRef和React.forwardRef
useRef
useRef可以接受一个默认值,并返回一个含有current属性的可变对象;
使用场景:
1、获取子组件的实例(子组件需为react类继承组件);
2、获取组件中某个DOM元素;
3、用做组件的全局变量,useRef返回对象中含有一个current属性,该属性可以在整个组件色生命周期内不变,不会因为重复render而重复申明,类似于react类继承组件的属性this.xxx一样。
原因:由于useState保存的变量会触发组件render,而使用useRef定义全局变量不会触发组件render
import React, {
useState, useRef } from 'react';function Example() {
const [count, setCount] = useState(0);// 获取DOM元素spanconst spanEl = useRef(null);const getSpanRef = () => {
console.log(2, spanEl.current)};// 获取react类组件实例const sunEl = useRef(null);const getRefBySun = () => {
console.log(1, sunEl.current)};// 全局变量isClick,默认值false赋予isClick.current,相当于react类组件的this.isClick = falseconst isClick = useRef(false);const addCount = () => {
if (!isClick.current) {
setCount(count + 1)isClick.current = true}};return (<><button onClick={
addCount}>addCount</button><Sun ref={
sunEl} count={
count} /><span ref={
spanEl}>我是span</span><button onClick={
getSpanRef}>获取DOM元素Span</button><button onClick={
getRefBySun}>获取Sun组件</button></>);
}
//Sun子组件
class Sun extends React.Component {
render () {
const {
count } = this.propsreturn (<div>{
count }</div>)}
}
export default Example;
React.forwardRef:
使用场景:比较少用,用于在父组件获取子组件的DOM元素作为自己的ref,然后操作子组件的DOM元素
import React, {
useRef } from 'react';function Example() {
//refconst inputEl = useRef(null);const onButtonClick = () => {
console.log(inputEl)inputEl.current.focus();};return (<><TextInputWithFocusButton ref={
inputEl} onButtonClick={
onButtonClick} /><span>我是span</span></>);
}
// 子组件
const TextInputWithFocusButton = (props) => {
return (<><input ref={
props.ref} type="text" /><button onClick={
props.onButtonClick}>Focus the input</button></>);
}
export default Example;
上面代码报错:Warning: TextInputWithFocusButton: ref is not a prop
.
需使用React.forwardRef包裹注入一个新的ref,修改TextInputWithFocusButton组件如下:
const TextInputWithFocusButton = React.forwardRef((props, ref) => {
return (<><input ref={
ref} type="text" /><button onClick={
props.onButtonClick}>Focus the input</button></>);
})
useContext
使用场景:useContext需要和React.createContext结合起来使用,解决夸组件间的数据传递问题,类似redux。
使用useContext方式
import React, {
useRef, useContext } from 'react';
//Context,createContext接收一个参数为默认值
const ThemeContext = React.createContext('white');
const AgeContext = React.createContext();function Example() {
return (<><ThemeContext.Provider value={
'blue'}><div><ChildOfContext /></div></ThemeContext.Provider><span>我是span</span></>);
}
// 子组件
const ChildOfContext = (props) => {
console.log("进入了子组件ChildOfContext")return (<div>这里是子组件ChildOfContext<GrandChildOfContext /></div>)
}
// 孙子组件
const GrandChildOfContext = (props) => {
console.log("进入了孙子组件GrandChildOfContext")const color = useContext(ThemeContext);return (<div>这里是子组件GrandChildOfContext颜色是:{
color} </div>)
}
export default Example;
使用传统React API方式,修改孙子组件如下:
const GrandChildOfContext = (props) => {
console.log("进入了孙子组件GrandChildOfContext")// 使用 Consumer 从上下文中获取 valuereturn (<ThemeContext.Consumer>{
value => (<div>这里是子组件GrandChildOfContext颜色是:{
color} </div>)}</ThemeContext.Consumer>)
}
使用contextType方式,只支持React类组件,修改孙子组件如下:
class GrandChildOfContext extends React.Component {
static contextType = ThemeContextrender () {
const color = this.contextreturn (<div>这里是子组件GrandChildOfContext颜色是:{
color}</div>)}
}
多个context嵌套使用时使用useContext方式
import React, {
useRef, useContext } from 'react';
//Context,createContext接收一个参数为默认值
const ThemeContext = React.createContext('white');
const AgeContext = React.createContext();function Example() {
return (<><ThemeContext.Provider value={
'blue'}><div><ChildOfContext /></div></ThemeContext.Provider><span>我是span</span></>);
}
// 子组件进行嵌套一层Context
const ChildOfContext = (props) => {
console.log("进入了子组件ChildOfContext")return (<div>这里是子组件ChildOfContext<AgeContext.Provider value={
21}><div><GrandChildOfContext /></div></AgeContext.Provider></div>)
}
// 孙子组件
const GrandChildOfContext = (props) => {
console.log("进入了孙子组件GrandChildOfContext")const color = useContext(ThemeContext);const age = useContext(AgeContext);return (<div>这里是子组件GrandChildOfContext颜色是:{
color} 嵌套Context年龄是:{
age} </div>)
}
export default Example;
使用传统React API方式,修改孙子组件如下:
// 传统reactAPI方式
const GrandChildOfContext = (props) => {
console.log("进入了孙子组件GrandChildOfContext")return (<ThemeContext.Consumer>{
color => (<div>这里是子组件GrandChildOfContext颜色是:{
color} <AgeContext.Consumer>{
age => (<div>嵌套Context年龄是:{
age} </div>)}</AgeContext.Consumer></div>)}</ThemeContext.Consumer>)
}
contextType方式不支持多个Context嵌套,由此可见,useContext方式最为简单
官方提醒:接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的
context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext
provider 的 context value 值。即使祖先使用 React.memo 或
shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。
因此,<MyContext.Provider>包裹的组件越多,嵌套越多,对子组件的影响越大,导致不必要的重渲染太多,所以Context最好就近用,别跨那么多级组件
Hook 使用规则
Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。 只能在 React 的函数组件中调用 Hook。不要在其他
JavaScript 函数中调用。
import React, {
useState } from 'react';let isName = true;
function Example() {
//报错:React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render // 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。const [count, setCount] = useState(0);if(isName){
const [name, setName] = useState('名字');isName = false;}return (<div><p>You clicked {
count} times</p><button onClick={
() => setCount(count + 1)}>Click me</button></div>);
}