- Hook 是 React 16.8 的新增特性,所以使用Hook 时要保证React版本在16.8及以上。
npm info react versions
,用来获取react所有的版本号;- 有了Hook我们就不需要在
class
里面去声明状态了
useState
- 具体内容可阅读上一节
useReducer
1、基础用法
- 用来践行Flux/Redux的思想
- 步骤:
①创建初始值initalState;
②创建所有操作reducer(state, action);
③传给useReducer,得到读写API;
④调用写({type: '操作类型'}); - 可看下面代码:
import React, { useReducer } from "react";
import ReactDOM from "react-dom";
const initFormValue = {
name: "",
age: 18,
nationality: "汉族"
};
function reducer(state, action) {
switch (action.type) {
case "patch":
return { ...state, ...action.formData };
case "reset":
return initFormValue;
default:
throw new Error();
}
}
function App() {
const [formData, dispatch] = useReducer(reducer, initFormValue);
const onReset = () => {
dispatch({ type: "reset" });
};
return (
);
}
const rootElement = document.getElementById("root");
ReactDOM.render( , rootElement);
2、用useReducer代替Redux
- 步骤
①将数据集中在一个store对象里;
②再将所有操作集中在reducer里;
③创建一个Context,const Context = React.createContext(null)
,一般都要传个null;
④将reducer、store传给useReducer,得到读和写API,const [state, dispatch] = useReducer(reducer, store)
;
⑤将第四步的内容放到第三步的Context里;
⑥用Context.Provider将Context提供给所有它所包住的组件,
;...
⑦各个组件用useContext获取读写API,const {state, dispatch} = useContext(Context)
,这里的Context是第三步里创建的Context;
总的来说,useReducer是useState的复杂版;
useContext
1、上下文
- 上下文就是局部的全局变量;其实也就是作用域啦~能在哪个范围内使用;
2、使用方法
①先创建上下文,const C = React.createContext(null)
;
②使用
圈定作用域,注意value里一般给一个读值,一个写值,例如:value={{n: n, setN: setN}}
;
③在作用域内使用const { n, setN } = useContext(C)
来使用n和setN,注意:这里是从useContext解构,所以是{},不是[];
3、useContext不是响应式的
- useContext改变数据的时候是自顶向下逐级改变而做到的,比如下方代码,在儿子中setN改变n,实际上是从App组件上,自上而下,把子组件中使用n的组件都渲染一遍,也就是说每次都会重新渲染一遍App
- 而响应式则是,若爸爸中也使用了n,儿子中setN改变n,爸爸中的n会立即更新,不会通过App自上而下渲染一遍(vue3是用这种方法,响应式变化数据);
import { useContext, useState, createContext } from 'react';
const C = createContext(null);
function App() {
const [info, setInfo] = useState(null);
return (
);
}
function Baba() {
return (
我是爸爸
);
}
function Son() {
const { info, setInfo } = useContext(C);
return (
我是儿子,info: {info}
);
}
export default App;
useEffect
1、副作用
- 对环境的改变就叫副作用;比如修改document.title;
- 实际上叫afterRender更贴切一些,因为是每次render后执行;
2、用途
①useEffect(()=>{}, [])
,在第一次变化后执行,相当于componentDidMount;
②useEffect(()=>{}, [n])
,只有在n变化的时候才执行(包含第一次渲染),相当于componentDidUpdate;
③useEffect(()=>{})
,任何一个变量变化就执行;
④useEffect(()=>{.... return () => {}})
,通过添加一个return来执行组件要消失时刻的操作,相当于componentWillUnmount;
上面这几种用途可同时存在;
可同时使用多个useEffect,它们之间是不冲突的;且是按照顺序来执行;
useLayoutEffect
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
const [value, setValue] = useState(0);
// 这里再换成useLayoutEffect看有什么不同呢
useEffect(() => {
document.querySelector("#x").innerText = `value: 1000`;
}, [value]);
return (
setValue(0)}>
value: {value}
);
};
ReactDOM.render( , document.querySelector("#root"));
1、含义
- useEffect是在浏览器渲染完成后执行;
- useLayoutEffect是在浏览器渲染前执行;
- 看上面代码,当为useEffect时,可以看到页面上,会先显示0再很快地换为1000;当换为useLayoutEffect时,你就看不到0的显示,会直接显示为1000;
2、特点
- useLayoutEffect总是比useEffect先执行;
- 如果要使用useLayoutEffect,那么useLayoutEffect里的任务最好是影响了layout;
3、总结
- 所以为了用户体验,最好用useEffect;(useLayoutEffect会影响到用户看到画面变化的时间,因为用户就是想先看到画面啊!!!所以为什么非得要 useLayoutEffect在中间截胡一下,还要延迟用户看到画面的时间呢!)
useMemo
1、React.Memo()
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
return (
);
}
function Child(props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return child: {props.data};
}
- 看上面代码,每次更新n的时候,即使m没有变化,Child组件也会跟着渲染一边,这多耗CPU啊,所以得想一个办法,m不变化的时候Child组件不跟着渲染;所以可以用
React.Memo()
把Child组件包起来,看下面代码~
const Child = React.memo((props) => {
console.log("child 执行了");
console.log("假设这里有大量代码");
return child: {props.data};
});
2、useMemo()用法
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(1);
const onClick = () => {
setN(n + 1);
};
const onClickChild = () => {
console.log("点击child 了");
};
return (
);
}
const Child = React.memo((props) => {
console.log("child 执行了");
console.log("假设这里有大量代码");
return child: {props.data};
});
- 有一个bug,当你给Child组件添加了一个监听函数后,在改变n时,Child组件又跟着一起变了!!!这是因为App在运行时,onClickChild函数每次都会生成一个新的函数,新旧函数地址不一样!!!
- 那怎么办呢?用React.useMemo()把onClickChild函数包起来!!!
const onClickChild = React.useMemo(() => {
return () => {
console.log(m, "点击child 了");
};
}, [m]);
3、useMemo()的特点
- 它的第一个参数是
() => value
,这个value可能是个函数也可能是个对象; - 第二个参数是依赖[m, n];
- 当依赖变化的时候就计算出新的value,如果依赖没有变化,就还用之前的value;
- 这不就跟vue2的computed很像了嘛!!!
注意!如果你的value是个函数,你就要这么写,
React.useMemo(()=> (x)=> console.log(x))
,这是一个返回函数的函数,是不是很难用,所以就有了useCallback~~~
4、useCallback
- 可以把最外层的那个函数省略掉,
React.useCallback((x)=> console.log(x), [m])
等价于React.useMemo(()=> (x)=> console.log(x), [m])
useMemo与useCallback的区别:
- useMemo主要用来缓存计算结果,为的是节省计算开销(跟vue2的computed很像)
- useCallback主要用来缓存函数(也就是函数的引用地址),节省函数的不必要的重新创建(或刷新)开销;
useRef
1、目的
- 当我们需要一个值,它需要在组件不断render的时候保持不变;
- 初始化:
const count = React.useRef(0)
; - 读取:
count.current
; - 为什么需要current呢?因为这样就能保证每次渲染后的useRef都是同一个值(因为只有引用可以做到);
2、count.current的值变化时不会自动render
- 如果想让count.current的值变化时可以自动render:
①调用一下setN();
②用vue3,vue3的ref改变会自动render,详见文档
forwardRef
1、什么时候用到它呢
- 当父组件需要拿到子组件某个实际的DOM元素时需要用到它;
- 当你要给函数组件上传一个ref属性,那么声明这个函数组件的时候,需要用forwardRef在外面套一层;
- 并且套在forwardRef里面的函数组件会多接受一个参数ref,即里面的函数会接受两个参数:props和ref;
- 为什么会有forwardRef呢?因为props不包含ref;那为什么props不包含ref呢?因为大多数时间都用不到;
- 文档
import React, { useRef, forwardRef } from "react";
function App() {
//buttonRef 可以拿到子组件 中真实的DOM元素
const buttonRef = useRef(null);
return (
winwin
);
}
const Button2 = forwardRef((props, ref) => {
return ;
});
export default App;
useImperativeHandle
- 名字起得不好,应该叫setRef;
- 具体可看文档和PPT上的例子;
useState / useReducer ===> 它们里面的n每次都变;
useMemo / useCallback ===> 当依赖[m]变的时候,返回的值就变化;
useRef ===> 它的值永远不变;
自定义Hook
- 通过自定义Hook,可以把组件逻辑提取到重用的函数里;
- 自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook;
- 封装数据操作,return出来增删改查的接口,例子,又或者因为它是一个函数,所以可以直接传参调用它;
- 注意在用自定义Hook的时候,声明的时候是对象!!不是数组!!!
const {list, setList} = useList(null)
- 项目中尽量使用自定义Hook;
Stale Closure
- Stale Closure:过时的闭包;用来描述你的函数引用的变量是之前产生的变量;
- 看下这篇文章的几段代码,你就明白了;
总结
- useContext,用来把读写接口给整个页面用;
- useReducer,专门给Redux的用户设计的,我们甚至可以不用它;
- useMemo,和React.Memo配合使用;
- forwardRef并不是一个Hook;