Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
注意:所有的Hooks都是用use来声明的。
const [state, setState] = useState(initialState);
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
考虑到在blog中不好体现代码更改的位置,小迪才用github托管代码,大家可以查看github,看到详细版本修改过程,搭配博客学习。
注意:上一篇已经详述了useState、useEffect、useRef,现在总结后面的内容了。
code\project\study-hooks\react-app\src\hooks\memo.js
import React, { useState, useMemo} from 'react';
function Memo() {
const [name,setName] = useState("leo");
const [age,setAge] = useState(10);
let age2 = (()=>{
console.log(1);
return age < 18?"未成年":"成年人";
})();
return (<div>
姓名:{name},<br />
年龄:{age},<br />
年龄阶段:{age2},<br />
<button onClick={()=>{
setAge(age+2);
}}>长大</button>
</div>);
}
export default Memo;
存在问题:当并不需要修改年龄阶段的时候,也会调用该函数。每次组件更新,都会执行该函数。而我们只需要某些时候,即age
大于18
之后,才需要调用函数。
这个时候就用到了useMemo
,它的作用就是优化计算性能
的,如某些函数仅仅依赖某些状态的改变,才去执行,这个时候就可以把它放入useMemo
中了。
只有某些状态发生了变化,才能执行useMemo
。
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.07-1
Branch: bhStudy-Hookscommit description:as0.07-1-example07-1(useMemo ——引例)
tag:as0.07-1
useMemo
的写法和useEffect
写法非常像,接收的也是一个函数,第二个参数也是这个函数执行需要依赖于谁,即谁改变了才会执行该函数。
有人疑问为啥不用useEffect
实现呢?
import React, {useState, useMemo, useEffect} from 'react';
function Memo() {
const [name,setName] = useState("leo");
const [age,setAge] = useState(10);
let age2 = useMemo(()=>{
console.log(1);
return age < 18?"未成年":"成年人";
},[]);
useEffect(()=>{
console.log("更新完成之后!")
},[])
return (<div>
姓名:{name},<br />
年龄:{age},<br />
年龄阶段:{age2},<br />
<button onClick={()=>{
setAge(age+2);
}}>长大</button>
</div>);
}
export default Memo;
因为执行阶段不一样,useEffect
副作用钩子是当我们组件更新完成之后才执行了,而useMemo
是更新前执行。
import React, {useState, useMemo, useEffect} from 'react';
function Memo() {
const [name,setName] = useState("leo");
const [age,setAge] = useState(10);
let age2 = useMemo(()=>{
console.log(1);
return age < 18?"未成年":"成年人";
},[age]);
useEffect(()=>{
console.log("更新完成之后!")
},[age])
console.log("开始更新了");
return (<div>
姓名:{name},<br />
年龄:{age},<br />
年龄阶段:{age2},<br />
<button onClick={()=>{
setAge(age+2);
}}>长大</button>
</div>);
}
export default Memo;
这就能完整看到整个阶段更新前、更新开始、更新后了。
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.07-2
Branch: bhStudy-Hookscommit description:as0.07-2-example07-2(useMemo ——其与useEffect区别)
tag:as0.07-2
useMemo会在视图更新之前,就调用了,而在useEffect中更改,不会直接返回在视图中,它得等下一次更新,因为useEffect是更新之后执行的。
import React, {useState, useMemo, useEffect} from 'react';
function Memo() {
const [name,setName] = useState("leo");
const [age,setAge] = useState(10);
let age2 = useMemo(()=>{
console.log(1);
return age < 18?"未成年":"成年人";
},[age]);
return (<div>
姓名:{name},<br />
年龄:{age},<br />
年龄阶段:{age2},<br />
<button onClick={()=>{
setAge(age+2);
}}>长大</button>
</div>);
}
export default Memo;
感觉跟概念不一致,每次更新的时候,方法还是在执行啊?
因为在这里仅仅是依赖age
,但是没有设置详细的依赖条件。
现在我们设置age < 18
刚开始,这个返回true
,直到变为false
才会调用,之后状态一直是false
了,不会再切回true
了,因此也不会再调用该函数了。
import React, {useState, useMemo, useEffect} from 'react';
function Memo() {
const [name,setName] = useState("leo");
const [age,setAge] = useState(10);
let age2 = useMemo(()=>{
console.log(1);
return age < 18?"未成年":"成年人";
},[age < 18]);
return (<div>
姓名:{name},<br />
年龄:{age},<br />
年龄阶段:{age2},<br />
<button onClick={()=>{
setAge(age+2);
}}>长大</button>
</div>);
}
export default Memo;
这就是useMemo
的应用了,它能极大地优化我们的计算性能。
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.07-3
Branch: bhStudy-Hookscommit description:as0.07-3-example07-3(useMemo ——应用原理)
tag:as0.07-3
建议如果涉及复杂运算的函数,又仅仅依赖几个状态的改变,就可以用useMemo
来优化了。
它是useMemo
的变体,并且极其相似。
useMemo
除了第一个参数是函数外,这个函数里还可以嵌套useMemo
,即函数返回一个useMemo
,或者可以返回一个函数。
import React, {useState, useMemo} from 'react';
function CallBack() {
const [name,setName] = useState("leo");
const [age,setAge] = useState(10);
let age2 = useMemo(()=>{
// 返回一个函数
return ()=>age<18?"未成年":"成年人";
},[age < 18]);
return (<div>
姓名:{name},<br />
年龄:{age},<br />
年龄阶段:{age2()},<br />
<button onClick={()=>{
setAge(age+2);
}}>长大</button>
</div>);
}
export default CallBack;
运行可能没啥问题,但是在处理复杂业务的时候,有可能useMemo
第一个函数参数返回的是一个useMemo
或者是一个函数,这样代码就有点复杂,因此我们可以对其进行简化。
直接把返回的函数内容传给useCallback
即可。
import React, { useState, useCallback} from 'react';
function CallBack() {
const [name,setName] = useState("leo");
const [age,setAge] = useState(10);
let age2 = useCallback(()=>age<18?"未成年":"成年人",[age < 18]);
return (<div>
姓名:{name},<br />
年龄:{age},<br />
年龄阶段:{age2()},<br />
<button onClick={()=>{
setAge(age+2);
}}>长大</button>
</div>);
}
export default CallBack;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.08
Branch: bhStudy-Hookscommit description:as0.08-example08(useCallback)
tag:as0.08
useMemo
和 useCallback
接收的参数都是一样,第一个参数为回调 第二个参数为要依赖的数据
共同作用:
1.仅仅 依赖数据
发生变化, 才会重新计算结果,也就是起到缓存的作用。
两者区别:
1.useMemo
计算结果是 return
回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态
2.useCallback
计算结果是 函数
, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。
context
createContext、Provider、Consumer、contextType
Context作用:跨层传递信息
之前我们组件间传递消息是首先会有一个子组件,先演示类组件
code\project\study-hooks\react-app\src\hooks\context.js
import React, {Component} from 'react';
class Child extends Component {
render(){
return (
<strong>这是祖先传下来的宝贝: {this.props.info}</strong>
)
}
}
class Parent extends Component {
render(){
return (<p>
<Child info={this.props.info}/>
</p>);
}
}
class Context extends Component {
render(){
return <div>
<Parent
info ={"今天天气不错!"}
/>
</div>
}
}
export default Context;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.09-1
Branch: bhStudy-Hookscommit description:as0.09-1-example09-1(useContext ——类组件消息传递)
tag:as0.09-1
观察以上过程还是相对非常麻烦的,可能有人就去用redux
,但是react
能不能有便捷方法呢?这就用到了createContext
这个方法了。
import React, {Component, createContext} from 'react';
console.log(createContext())
我们先看一下它的返回值。它返回一个对象,里面主要有两个对象,一个是Consumer
,一个是Provider
。它实际就是通过Consumer
和Provider
传递消息的。
我们先将Consumer
和Provider
解构出来。
Provider
包装父组件传递参数,接收的对象,用Consumer
包装,Consumer
只能接收一个函数,从context
参数中取参数。
import React, {Component, createContext} from 'react';
let {Provider,Consumer} = createContext();
class Child extends Component {
render(){
return (
<Consumer>
{(context)=>{
console.log(context);
return <strong>这是祖先传下来的宝贝:{context.info} </strong>
}}
</Consumer>
)
}
}
class Parent extends Component {
render(){
return (<p>
<Child/>
</p>);
}
}
class Context extends Component {
render(){
return <div>
<Provider value={{info :"今天天气不错"}}>
<Parent />
</Provider>
</div>
}
}
export default Context;
这就传递成功了,如果还有其他地方需要使用的话,不管传递几层,只需要Consumer
包装即可。(React16.3新增的CreateContext
)
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.09-2
Branch: bhStudy-Hookscommit description:as0.09-2-example09-2(useContext ——类组件消息传递-CreateContext使用)
tag:as0.09-2
Provider
负责向下传递数据,Consumer
负责在子孙级中去接收Provider传下的数据,以上写法还是有些麻烦,有没有更简易的写法呢?我们看看类式组件
更简便的写法。
不用Consumer
传递数据,在类式组件中可以申明一个静态属性contextType
给它赋值后,其实它就是类式组件中context
属性。
import React, {Component, createContext} from 'react';
let myContext = createContext();
class Child extends Component {
// 不用Consumer传递数据,在类式组件中可以申明一个静态属性contextType给它赋值后,其实它就是类式组件中context属性
static contextType = myContext;
render(){
console.log(this.context);
return (
<strong>这是祖先传下来的宝贝: {this.context.info}</strong>
)
}
}
class Parent extends Component {
render(){
return (<p>
<Child/>
</p>);
}
}
class Context extends Component {
render(){
return <div>
<myContext.Provider value={{info :"今天天气不错"}}>
<Parent />
</myContext.Provider>
</div>
}
}
export default Context;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.09-3
Branch: bhStudy-Hookscommit description:as0.09-3-example09-3(useContext ——类组件消息传递-CreateContext简便方法使用)
tag:as0.09-3
用函数式组件使用Contex
import React, {Component, createContext} from 'react';
let myContext = createContext();
class Child extends Component {
// 不用Consumer传递数据,在类式组件中可以申明一个静态属性contextType给它赋值后,其实它就是类式组件中context属性
static contextType = myContext;
render(){
return (
<strong>这是祖先传下来的宝贝: {this.context.info}</strong>
)
}
}
function Child2(){
return (
<myContext.Consumer>
{
context=>{
console.log(context);
return <div><strong>这是祖先传下来的宝贝: {context.info}</strong></div>
}
}
</myContext.Consumer>
)
}
class Parent extends Component {
render(){
return (<div>
<Child/>
<Child2/>
</div>);
}
}
class Context extends Component {
render(){
return <div>
<myContext.Provider value={{info :"今天天气不错"}}>
<Parent />
</myContext.Provider>
</div>
}
}
export default Context;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.09-4
Branch: bhStudy-Hookscommit description:as0.09-4-example09-4(useContext ——类组件消息传递-CreateContext在函数式组件中的使用)
tag:as0.09-4
但是每次都用Consumer
包装会很麻烦,我们寻找一个简便方式借助钩子中的useContext
,useContext
必须传入参数,确定它是哪一个context
的对象,其作用就是接受context
传来的信息。
import React, {Component, createContext, useContext} from 'react';
let myContext = createContext();
class Child extends Component {
// 不用Consumer传递数据,在类式组件中可以申明一个静态属性contextType给它赋值后,其实它就是类式组件中context属性
static contextType = myContext;
render(){
return (
<strong>这是祖先传下来的宝贝: {this.context.info}</strong>
)
}
}
function Child2(){
let {info} = useContext(myContext);
return (
<div><strong>这是祖先传下来的宝贝: {info} </strong></div>
)
}
class Parent extends Component {
render(){
return (<div>
<Child/>
<Child2/>
</div>);
}
}
class Context extends Component {
render(){
return <div>
<myContext.Provider value={{info :"今天天气不错"}}>
<Parent />
</myContext.Provider>
</div>
}
}
export default Context;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.09-5
Branch: bhStudy-Hookscommit description:as0.09-5-example09-5(useContext ——类组件消息传递-CreateContext在函数式组件中的使用-useContext)
tag:as0.09-5
context
的使用基本就是这么多,但是在实际项目中需要注意小迪不建议直接使用Context
,除非对React
的数据流非常熟悉,否则别使用它,还是建议使用React-Redux
。
本身React-Redux
就是基于Context
实现的。在Hooks
中useContext
,和配合 useReducer
,可以实现一个简易的Redux
。
实现一个简易的Redux
Redux
开发实际要基于一个纯函数reducer
开发
import React, {useReducer} from 'react';
// state 状态 action 操作动作
function reduce(state = 0,action){
switch(action.type){
case "add":
state += 1
break;
case "minus":
state -= 1
break;
}
return state;
}
function Reduce(){
// 第一个参数reduce,第二个参数是函数初始值
console.log(useReducer(reduce,0))
return (
<div>
</div>
);
}
export default Reduce;
返回值,第一个是state
值,第二个是dispatch
,修改state
的方法。
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.10-1
Branch: bhStudy-Hookscommit description:as0.10-1-example10-1(useReducer ——查看useReducer返回值)
tag:as0.10-1
Redux
是在最外层把状态传递进去,哪一层需要,就对哪一层进行包装,然后包装数据。
实现简易redux
,没有高阶组件封装
import React, {useReducer, createContext, useContext} from 'react';
let myContext = createContext();
// state 状态 action 操作动作
function reduce(state = 0,action){
switch(action.type){
case "add":
state += 1
break;
case "minus":
state -= 1
break;
}
return state;
}
function Child(){
let {state,dispatch} = useContext(myContext);
return (
<div>
{/*减法*/}
<button
onClick={()=>{
dispatch({
type: "minus"
});
}}
>-</button>
<span> {state} </span>
{/*加法*/}
<button
onClick = {()=>{
dispatch({
type: "add"
});
}}
>+</button>
</div>
)
}
function Reduce(){
// 第一个参数reduce,第二个参数是state初始值
let [state,dispatch] = useReducer(reduce,0);
return (
<myContext.Provider value = {{
state,
dispatch
}}>
<Child />
</myContext.Provider>
);
}
export default Reduce;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.10-2
Branch: bhStudy-Hookscommit description:as0.10-2-example10-2(useReducer ——实现简易redux)
tag:as0.10-2
很多操作都有上一步和下一步这个操作,如切换选项卡、幻灯片上一张下一张,这些操作都是最终一个数字的改变。
我们把这个通用逻辑方式摘出来,写成一个hooks
。
要求hooks
函数组件名称必须以use
开头,过程和返回值可以根据自己的需求去定义。
上一张和下一张改变数字,都有哪些改变呢?
实际上是在当前数字的基础上,让它进行加减。useCount
除了传当前值,还需要有最大值和最小值作为限制。目前先不弄这么复杂,就做最基本的逻辑,即加减。
注意在自定义的hooks
中也可调用其他的hooks
。
import React, {useState} from 'react';
function useCount(init){
let [count,setCount] = useState(init);
function add(){
count++;
setCount(count);
}
function minus(){
count--;
setCount(count);
}
return [count,add,minus,setCount];
}
function Hook(){
let [count,add,minus,setCount] = useCount(0);
return (<div>
<button
onClick={()=>{
minus();
}}
>-</button>
<span> {count} </span>
<button
onClick = {()=>{
add();
}}
>+</button>
<button onClick={()=>{
setCount(5);
}}>自定义设置</button>
</div>);
}
export default Hook;
参考:https://github.com/6xiaoDi/Projec-React-hooks-enterprise-level-mbapp/tree/as0.11
Branch: bhStudy-Hookscommit description:as0.11-example11(自定义hooks)
tag:as0.11
只在最顶层(组件的最顶层,不能在if
或者循环
中、子函数
使用hooks)使用 Hook(Hook本身包括副作用可能会有一些相关依赖,即不写在最顶层,中间会造成一些依赖导致出一些错误)
只在 React
函数中调用 Hook
React
函数组件中React
(自定义)Hook
中(如在类组件中使用hook可能会报错)