react在16版本之后,加入了fiber架构,官方推荐使用纯函数组件,为此react-hook应运而生。
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 (
You clicked {count} times
);
}
export default Example;
const [ count, setCount ] = useState(() => {
return props.count || 0
})
使用场景:由于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}
>
);
}
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}
>
);
}
export default Example;
使用场景: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后,都会执行该useEffect
useEffect(() => {
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 (
You clicked {count} times
);
}
export default Example;
官方提示:与 componentDidMount 或 componentDidUpdate 不同,useEffect是异步的,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。官方建议尽可能使用useEffect,effect 不需要同步地执行
在个别情况下(例如测量布局,页面状态值闪烁bug时),才使用useLayoutEffect代替useEffect, 形成同步,在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制
import React, { useState, useCallback } from 'react';
function Example() {
const [count, setCount] = useState(0);
const [list, setList] = useState([]);
return (
You clicked {count} times
);
}
const Child = (props) => {
console.log("进入了组件child")
return (
这里是child:list为{props.list.join(',')}
)
}
export default Example;
此时,无论是改变count状态值,还是name状态值,Child组件都会重新渲染,如果我们想让Child组件在其props传入的值,即list改变时才渲染
用React.memo解决,修改Child如下:
...
const Child = React.memo((props) => {
console.log("进入了组件child")
return (
这里是child:{props.list.join(',')}
)
})
...
上面例子还存在一个问题,如果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 (
You clicked {count} times
);
}
const Child = React.memo((props) => {
console.log("进入了组件child")
return (
这里是child:list为{props.list.join(',')}
)
})
export default Example;
此时,无论是改变count状态值,还是list状态值,Child组件都会重新渲染
用useCallback解决,修改handleChange方法如下:
// 第二个参数为空数组,也可以指定依赖于某一状态值,即该状态值变化时才重新改变handleChange方法
const handleChange = useCallback(() => {
console.log(`selected`);
},[])// 或[list]
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 (
You clicked {count} times
);
}
const Child = React.memo((props) => {
console.log("进入了组件child")
return (
这里是child:list为{props.list.join(',')}, 计算总和:{props.memoizedValue}
)
})
export default Example;
import React, { useState, useRef } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 获取DOM元素span
const 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 = false
const isClick = useRef(false);
const addCount = () => {
if (!isClick.current) {
setCount(count + 1)
isClick.current = true
}
};
return (
<>
我是span
>
);
}
//Sun子组件
class Sun extends React.Component {
render () {
const { count } = this.props
return (
{ count }
)
}
}
export default Example;
import React, { useRef } from 'react';
function Example() {
//ref
const inputEl = useRef(null);
const onButtonClick = () => {
console.log(inputEl)
inputEl.current.focus();
};
return (
<>
我是span
>
);
}
// 子组件
const TextInputWithFocusButton = (props) => {
return (
<>
>
);
}
export default Example;
上面代码报错:Warning: TextInputWithFocusButton:
ref
is not a prop.
需使用React.forwardRef包裹注入一个新的ref,修改TextInputWithFocusButton组件如下:
const TextInputWithFocusButton = React.forwardRef((props, ref) => {
return (
<>
>
);
})
使用场景:useContext需要和React.createContext结合起来使用,解决夸组件间的数据传递问题,类似redux。
import React, { useRef, useContext } from 'react';
//Context,createContext接收一个参数为默认值
const ThemeContext = React.createContext('white');
const AgeContext = React.createContext();
function Example() {
return (
<>
我是span
>
);
}
// 子组件
const ChildOfContext = (props) => {
console.log("进入了子组件ChildOfContext")
return (
这里是子组件ChildOfContext
)
}
// 孙子组件
const GrandChildOfContext = (props) => {
console.log("进入了孙子组件GrandChildOfContext")
const color = useContext(ThemeContext);
return (
这里是子组件GrandChildOfContext
颜色是:{color}
)
}
export default Example;
const GrandChildOfContext = (props) => {
console.log("进入了孙子组件GrandChildOfContext")
// 使用 Consumer 从上下文中获取 value
return (
{value => (
这里是子组件GrandChildOfContext
颜色是:{color}
)}
)
}
class GrandChildOfContext extends React.Component {
static contextType = ThemeContext
render () {
const color = this.context
return (
这里是子组件GrandChildOfContext
颜色是:{color}
)
}
}
import React, { useRef, useContext } from 'react';
//Context,createContext接收一个参数为默认值
const ThemeContext = React.createContext('white');
const AgeContext = React.createContext();
function Example() {
return (
<>
我是span
>
);
}
// 子组件进行嵌套一层Context
const ChildOfContext = (props) => {
console.log("进入了子组件ChildOfContext")
return (
这里是子组件ChildOfContext
)
}
// 孙子组件
const GrandChildOfContext = (props) => {
console.log("进入了孙子组件GrandChildOfContext")
const color = useContext(ThemeContext);
const age = useContext(AgeContext);
return (
这里是子组件GrandChildOfContext
颜色是:{color}
嵌套Context
年龄是:{age}
)
}
export default Example;
// 传统reactAPI方式
const GrandChildOfContext = (props) => {
console.log("进入了孙子组件GrandChildOfContext")
return (
{color => (
这里是子组件GrandChildOfContext
颜色是:{color}
{age => (
嵌套Context
年龄是:{age}
)}
)}
)
}
contextType方式不支持多个Context嵌套,由此可见,useContext方式最为简单
官方提醒:接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的
当组件上层最近的
因此,
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 (
You clicked {count} times
);
}
结语:希望此文能帮大家轻松上手react-hook,码字辛苦,欢迎留赞,如果纰漏,请留言指出