前言
很早就知道hooks,但是那个时候并没有去使用,就是大概的了解了下,到后来使用了hooks,但是用的不深,总是对一些依赖不知道怎么去处理,也只会用useState,所以经过几篇文章的总结,对hooks有了一个更深的了解,做个记录方便以后查阅
传统的组件
我们使用最多的就是class组件,这种类型的组件我觉得使用还是很有必要的,因为在写class组件的时候,你会了解es6的class相关知识,以及this的指向问题。但是由于class组件的过度语法糖话,导致越来越多的人更倾向于函数组件,但是函数组件最开始用法很单一,只是作为一个ui组件(无状态组件),为了在函数组件里面使用状态,hooks就应运而生了
useState
如果把useState做比喻的话,相当于class组件中的state和setState,他能够让我们定义状态,并且去改变状态,setState参数可以是一个函数,函数的参数是上一个状态值,我们看一下代码就能明白他的作用
import React, { useState } from "react";
const FuncText = () => {
const [count, setCount] = useState(0);
return (
<div onClick={() => setCount(count + 1)}>我是count,自动加1:{count}</div>
);
};
export default FuncText;
解释
count 相当于class组件的this.state.count,setCount相当于setState({count: count + 1}),其中count默认值是0
注意点
上面这段代码其实还是很好理解,但是有一个点需要注意,就是useState里面如果是一个引用类型的值,比如数组,对象的时候。如果不生成一个新的引用,他是不对触发更新,因为他觉得值没有改变,如下
import React, { useState } from "react";
const FuncText = () => {
console.log("第二次我不会触发");
const [count, setCount] = useState({ name: "json" });
//const [count, setCount] = useState({ count: [0] });
return (
<div
onClick={() => {
count.name = "xiaoming";
setCount(count); // 不会触发
setCount({ ...count}); // 会触发
}}
>
我是count,自动加1:{JSON.stringify(count)}
</div>
);
};
export default FuncText;
useCllback
useCallback接受一个函数,返回一个函数,他也可以有依赖,如果依赖变了就会返回一个新函数,如果没有变,就会一直是旧函数,那么useCllback在哪里可以被用到了,我们有时候会遇到,把一个函数当作依赖,但是这个函数可能不需要改变,(那么为什么还要把他当依赖了,因为我们可能要复用这个函数,就会把这个函数提到外面,一旦提出去的话lint检查规则,会认为他是一个依赖)
但是一旦你把函数当作依赖,每次渲染函数会变化,那么这个组件就会出现死循环了,因为每次组件改变,函数都是新的,那么如果确保函数一直不变或者根据依赖值去变化,就是useCllback的用途看下面的代码
unction SearchResults() {
// ✅ Preserves identity when its own deps are the same
const getFetchUrl = useCallback((query) => {
return 'https://hn.algolia.com/api/v1/search?query=' + query;
}, []); // ✅ Callback deps are OK
useEffect(() => {
const url = getFetchUrl('react');
// ... Fetch data and do something ...
}, [getFetchUrl]); // ✅ Effect deps are OK
useEffect(() => {
const url = getFetchUrl('redux');
// ... Fetch data and do something ...
}, [getFetchUrl]); // ✅ Effect deps are OK
// ...
}
useReducer
useReducer和react中的redux一套流程比较相似,就是传入reducer和initState,然后给你返回state值和dispatch,那么为什么要用他了,他主要也是解决依赖问题,有时候我们的依赖值会一直发生变化,比如在定时器里面改变state的值,那么useEffect会执行多次,但是我们就只是想让值变化,怎么办了这个时候useReducer就有用了,看代码
const initState = {
sum: 0
};
const reducer = (state, action) => {
const { sum } = state;
switch (action.type) {
case "sumOne":
return { sum: sum + 1 };
default:
console.log("none");
}
};
const [state, dispatch] = useReducer(reducer, initState);
const { sum } = state;
useEffect(() => {
const timer = setInterval(() => {
// setSum(sum => sum + 1);
dispatch({ type: "sumOne" });
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
dispatch之后返回的state重新给到sum,所以值被改变
解释
这段代码摘自hooks文档上的,我们看到getFetchUrl 为了复用成了依赖,但是如果不用useCllback会死循环,所以用useCllback包裹起来,这样这个函数永远但是不变的(依赖是空),所以就达到了效果,那么如果想要随着query变化的话,就把query放入依赖数组即可
useEffect
useEffect应该是hooks里面最难也是最需要理解的一个点,useEffect第一个参数是需要处理的函数,第二个参数有几种状态,下面分别讲解
注意点1参数问题
参数什么都不传
当useEffect第二个参数什么都不传的时候,就相当于class组件的componentDidMount和componentDidUpdate,每次更新都会执行
参数为空数组([])
当参数为空数组的时候,只会执行一个,相当于componentDidMount
参数为有值得数组的时候[count]
有值得话相当于你传入了依赖,只有在依赖改变的时候才会去执行,当然第一次加载也会,相当于componentDidMount和componentWillReceiveProps,具体我们看一下下面的代码
import React, { useState, useEffect } from "react";
const FuncText = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("我是依赖count的打印");
}, [count]);
useEffect(() => {
console.log("我是空数组的打印");
}, []);
useEffect(() => {
console.log("我是什么都不依赖的打印");
});
return (
<div onClick={() => setCount(count + 1)}>我是count, 自动加1: {count}</div>
);
};
export default FuncText;
useEffect注意点2取消绑定问题
useEffect的取消绑定是在useEffect第一个函数里面返回一个函数,如果依赖是一个空数组,那么组件卸载的时候就会去执行取消绑定的操作,但是如果依赖没有,那么组件会在每一个渲染都会执行取消绑定函数,如果是一个有值得依赖,那么就会在每一次依赖值改变的时候,也会去执行取消绑定的操作
import React, { useState, useEffect } from "react";
const Text = props => {
const [sum, setSum] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setSum(sum + 1);
}, 1000);
return () => {
console.log("每一次sum改变都会执行");
clearInterval(timer);
};
}, [sum]);
return (
<span
onClick={() => {
console.log(sum, "count---");
setSum(sum + 1);
}}
>
我还能改变自己}
</span>
);
};
export default Text;
useEffect注意点3取值问题
useEffect里面的props和state的值,都是取得是当次渲染时候的值,注意不是最新的那次,因为useEffect用了闭包的原理,存储了每一个渲染时候props和state的值,所以每次useEffect都会拿到那个时候的值,而不会随着时间的变化去最新的,这里有一hooks文档上提供的例子https://codesandbox.io/s/lyx20m1ol
可以看一下,这里就不写例子了
useEffect注意点4依赖问题
useEffect我觉得最难的是依赖问题,如何去找依赖,如果不接触lint规则的话,最开始写这个的话,很难找出依赖,幸好我们有eslint-plugin-react-hooks 插件的exhaustive-depslint规则,可以帮助我们在写useEffect的时候,为我们找出依赖,如果不借助的话,我的思路是如果useEffect里面使用的变量,在useEffect里面没有定义,都可以看作需要依赖,那么正确找出依赖有什么好处了,看下面的代码
import React, { useState, useEffect } from "react";
const Text = props => {
const [sum, setSum] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setSum(sum + 1);
}, 1000);
return () => {
console.log("每一次sum改变都会执行");
clearInterval(timer);
};
}, []);
return (
<span
onClick={() => {
console.log(sum, "count---");
setSum(sum + 1);
}}
>
我还能改变自己}
</span>
);
};
export default Text;
代码给出警告说缺少sum依赖,一旦不写这个依赖,导致的问题是sum永远都是1,因为他只会执行一次,拿到的是初始化值0,但是如果加上依赖的话,我们每一个改变sum都会取消绑定和重新绑定的操作,代码优化不够,那么如何解决这个问题了有两种方式
方式1
还记得setSum的函数写法么,函数的参数就可以拿到之前的状态,所以我们可以这么写
useEffect(() => {
console.log("执行一次");
const timer = setInterval(() => {
setSum(sum => sum + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
useEffect只执行一次,但是sum每次都会正确改变
方式2
使用useReducer,上面已经讲解到了,看上面代码即可
useRef
useRef一般有两个作用一个是获取类组件的实例,一个是定义一个全局的变量,因为设置了useRef之后再去重新渲染他的值不会改变,看下面的代码
用法1
import React, { useState, useEffect, useRef } from "react";
import Text from "./Text";
const FuncText = () => {
const [count, setCount] = useState(0);
const textRef = useRef("");
useEffect(() => {
console.log("我是依赖count的打印");
}, [count]);
useEffect(() => {
console.log("我是空数组的打印");
}, []);
useEffect(() => {
console.log("我是什么都不依赖的打印");
});
return (
<div
onClick={() => {
console.log(textRef.current); // 获取到了text的值
setCount(count + 1);
}}
>
我是count, 自动加1: {count}
<Text ref={textRef} />
</div>
);
};
export default FuncText;
用法2
充当一个全局变量,还记得我们之前说的么,useEffect每次都是取到的是当次渲染的值,那么能不能做到像class那样拿到最新的了,这个就需要用到ref了,看下面的代码
useContext
useContext就相对比较简单一点就是获取context的值,减少props的传递,看代码如下
import React, { useContext } from "react";
import context from "../util/context";
const ContextText = () => {
const { count } = useContext(context);
return <div>我是context的count:{count}</div>;
};
export default ContextText;
// context.js
import React, { createContext } from "react";
const context = createContext(0);
export default context;
useMemo
useMemo就是hooks里面的一个优化手段,每次改变state的值得话,函数组件被重新渲染,那么如果有很多无关紧要的函数就会执行,用useMemo就会避免这个问题,如下代码
const { count } = props;
const [sum, setSum] = useState(0);
const add = useMemo(() => {
return sum + 1;
}, [sum]);
只有在cum改变时候才去执行useMemo代码,不然不去执行