函数组件的写法
const App2 = (props) => {
const {n,setN}=React.useState(0) //初始化state Hooks API
return <div></div>;
};
函数组件与class组件的不同
- 没有生命周期
- 简洁好写(优点)
- 没有this(这个实际上算是优点)
- 没有state
Hook API
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
state
为了解决函数组件没有state的问题,react在hook上做了useState的API
useState函数返回两个值,一个是初始化的数据,还有一个是设置该值的函数。它跟class组件上面的this.state是一样的。
但是要注意,它并像this.state会帮助我们自动合并,而且,初始化的值也不一定非要是一个对象
写法
const [n,setN]=React.useState(0) //初始化n为0
const [state,setState]=React.useState({n:0}) //state={n:0}
上面的setN和setState就类似于this.setState(),不过名字可以随便写,功能是一样的。
示例
以一个点击+1为例
const Example=(props)=>{
const [n,setN]=React.useState(0)
const add=()=>{
setN(n+1)
}
return <button onClick={add}>点击加1,此时n为{n}</button>
}
是不是超级简单?如果以后要创建很多state怎么办?可以使用对象,但是要注意手动合并
const Example=(props)=>{
const [state,setState]=React.useState({n:0,m:0})
const add=()=>{
//setState({n:state.n+1}) //** wrong!! **
setState({...state,n:state.n+1}) //** right!! **
}
return <>
<div>m:{state.m}</div>
<div>n:{state.n}</div>
<button onClick={add}>点击加1,此时n为{state.n}</button>
</>
}
上面使用了 ...运算符进行拷贝原来的state.m和state.n,然后覆盖了state.n,这种写法是正确的
Effect
react提供了React.useEffect这个API帮助我们模拟
class组件里面的生命周期。
React.useEffect就是一个函数,每次render之后就会触发这个函数,所以当我们这样写
React.useEffect(()=>{
console.log('render了一次')})
就会在每次render时触发。包括第一次render
模拟componentDidMount
React.useEffect(()=>{
console.log('第一次render')},[])
模拟componentWillUnmount
React.useEffect(()=>{
return ()=>{
console.log('删除dom时执行')
}
})
模拟componentDidUpdate
React.useEffect(() => {
console.log("n变了");
}, [state.n]); // 每次state.n变了都会执行钩子
//如果想要所有数据变化都执行,那么就不写第二个参数就行
注意:第一次渲染时,也会执行上面的钩子,因为第一次state.n从undefined到初始值也是一个update,我们可以增加一些逻辑来变相达成目标
const App2 = (props) => {
const [n, setN] = React.useState(0); //第一次为0
const add = () => {
setN(n + 1); // 每次点击后加1
};
React.useEffect(() => {
if (n > 0) { // 当它>0时才执行,因为第一次为0
console.log("n变了");
}
}, [n]);
return (
<>
<div>n:{n}</div>
<button onClick={add}>点击加1,此时n为{n}</button>
</>
);
};
上面的代码由于逻辑是初始值为0,每次点击后都+1,所以我增加了n>0这个判断,来避免第一次执行
但很多时候,由于应用场景的复杂导致我们没有办法来进行这样的逻辑判断。
所以更好的办法是自定义一个Hook函数来进行通用,而不是每次都根据业务逻辑来修改代码
自定义Hook
自定义Hook要求使用use开头的函数名以保持react函数组件风格的统一,不这样做就会报错
自定义模拟componentDidUpdate
我们采用useUpdate这样的函数名以避免react报错,目前的需求是这样的
- 第一次渲染时,不要执行
- 通用函数,以后都可以用
- 要求能监控到对应的数据变化
- 保持React.useEffect风格的统一,使用[]作为第二个参数,第一个参数是函数 所以可以根据react的API写一个函数
const useUpdate = (fn, array) => {
const [count, setCount] = React.useState(0); //初始化render次数为0
React.useEffect(() => {
setCount(count + 1);//当我们要监控的数据array变化时,就让count次数+1
}, array);
React.useEffect(() => {
if (count > 1) {//这里为什么要写>1?因为不管怎样,都会执行useEffect
fn();
}
}, [count]);//当次数+1时,执行函数
};
// 调用示例
useUpdate(()=>{console,log('xx')},[n])
上面的代码会有警告⚠️,虽然写法没错,但是react编译时,会有点问题,所以编译报了警告
按照警告,我们改成这样
const useUpdate = (fn, array) => {
const [count, setCount] = React.useState(0); //初始化render次数为0
React.useEffect(() => {
setCount((x) => x + 1); //react 提示要改成这样
}, [array]); //提示这里的参数一定要写成这样
React.useEffect(() => {
if (count > 1) {
fn();
}
}, [count, fn]); //提示要加入fn
};
//现在调用时,传入的参数只能是单个的值
// 调用示例
useUpdate(()=>{},n)