第一节 隆重介绍React Hooks
1、Hook是 React 16.8的新增特性。他可以让你在不编写class组件的情况下使用 state 以及其他的 React 特性。
2、Hook需紧记的是:
2.1、完全可选的。你无需要重新任何已有代码就可以在一些组件中尝试 Hook。但是你不要听信官网的是可选项,而是一定要学习,多使用,未来遇到的Hook写法将很多。
2.2、100%向后兼容的。Hook不包含任何破坏性改动。
2.3、现在可用。Hook发布于v16.8.0.
3、没有计划从 React 中移除 class。
4、Hook不会影响你对 React 概念的理解。恰恰相反,Hook 为已知的React概念提供了更直接的API:props、state、context、refs 以及生命周期。接下来,我们会发现 Hook 还提供了一种更强大的方式来组合他们。
5、React Hooks 解决了什么问题?
5.1、函数式组件不能使用 state , 一般只用于一些简单无交互的组件,用作信息展示。即:傻瓜式组件使用,如 果需要交互状态等复杂逻辑时就需要使用class组件了。
React Hooks让我们更好的拥抱函数式编程,让函数式组件也能使用 state 功能,因为函数式组件比class组件更简洁好用。未来有更多的函数式组件。
5.2、副作用问题
我们一般称数据获取、订阅、定时执行任务、手动修改ReactDOM 这些行为都可以称为副作用。
由于React Hook的出现,我们可以使用 useEffect 来处理组件副作用问题,我们的函数式组件也能进行副作用逻辑的处理了。
5.3、有状态的逻辑重用组件
5.4、复杂的状态管理
5.4.1、我们之前使用redux 、dva、mobx 第三方状态管理器来进行复杂的状态管理
5.4.2、现在我们可以使用 useReducer、useContext 配合使用实现复杂状态管理,不用再依赖第三方状态管理器
5.5、开发效率和质量问题
5.5.1、函数式组件比class组件简洁、开发的体验更好,效率更高同时应用的性能也更好。
第二节 详细介绍新特性useState
1、useState--组件状态管理钩子
1.1、useState能使函数组件能够使用state
1.2、基本使用如下所示
const [state,setState]=useState(initState);
// 参数:可以是任意类型。基本类型, 对象,函数 都没有问题。
1.2.1、state是要设置的状态
1.2.2、setState是更新state的方法,只是一个方法名,可以随意更改
1.2.3、initState是初始的state,可以是随意的数据类型,也可以是回调函数,但是函数必须要有返回值。
2、原始数据类型
UseStateFn.js
import React, { useState } from 'react'
export default function UseStateFn() {
const initCount = 0;
const [count, setState] = useState(initCount);
return (
<div>
<h6>6.1.1useState 处理 原始数据类型</h6>
<p>点击{count}次</p>
<button onClick={() => { setState(count + 1) }}>点击加1</button>
<button onClick={() => { setState(count - 1) }}>点击减1</button>
</div>
)
}
3、引用数据类型
UseStateFn2.js
import React, { useState } from 'react'
export default function UseStateFn() {
const initCharacter = {
performer: '苏有朋',
age: 23,
role: '张无忌'
}
// useState的参数为初始状态值,返回了一个长度为2的数组,
// 第一个元素就是我们类组件里的state对象,第二个元素是个函数,
// 作用相当于类组件里用的setState。
// 这两个元素的名字随便是什么都可以
const [character, setState] = useState(initCharacter);
// 此函数的参数为整个state的部分内容,
// 在调用setState的时候,用这个部分内容更新对应的老的内容
const upDateState = part => {
setState(oldState => (
{
...oldState,
...part
}
))
}
return (
<div>
<h6>6.1.2useState 处理 引用数据类型</h6>
<p>出演人物:{character.role}, 演员:{character.performer}, 年龄:{character.age}</p>
<p>
<button onClick={() => { upDateState({ role: '金毛狮王' }) }}>更换角色</button>
<button onClick={() => { upDateState({ age: character.age + 1 }) }}>年龄+1</button>
<button onClick={() => { upDateState({ performer: '吴孟达', age: 50, role: '金毛狮王' }) }}>更换演员</button>
<button onClick={() => { upDateState(initCharacter) }}>恢复角色</button>
</p>
</div>
)
}
第三节 详细介绍新特性useEffect
1、useEffect--副作用处理钩子
1.1、数据获取、订阅、定时执行任务、手动修改ReactDOM 这些行为都可以成为副作用。而useEffect就是为了处理这些副作用而生的。
1.2、useEffect也是componentDidMount、componentDidUpdate和componentWillUnmount这几个生命周期方法的统一
2、useEffect的基本使用如下所示
useEffect(callback,array);
2.1、callback:回调函数,作用是处理副作用逻辑
callback:可以返回一个函数,用作清理
useEffect(()=>{
// 副作用逻辑
xxxxxx
return ()=>{
// 清理副作用需要清理的内容
// 类似于componentWillUnmount,组件渲染 + 组件卸载前执行的代码
}
},[]);
2.2、第二个参数
很好理解(自己翻译)
第二个参数为 可选 : 监控范围。
如果不写:不定义范围,全部监控,每次渲染都执行!
如果写了:就监控里面的数据内容,数据变化就执行!
如果写了:空数组,监控为空,只执行1次!
不好理解(他人翻译)
array(可选参数):数组,作用是用于控制useEffect的执行
分为3种情况:
1.空数组,则会执行一次(即初次渲染render), 相当于componentDidMount
2.非空数组,useEffect会在 数组发生变化后执行
3.不填array,useEffect每次渲染都会执行
3、完整使用
UseEffectFn.js
import React, { useState, useEffect } from 'react'
const EffectChild = () => {
// 4、组件卸载:componentWillUnmount
useEffect((timer) => {
console.log('4、useEffect子组件首次加载');
timer = setInterval(() => {
console.log('定时器启动中...');
}, 1000);
return () => {
clearInterval(timer);
console.log('4.1、useEffect子组件卸载了');
}
}, []);
return (
<div>effect的个子组件</div>
)
}
export default function UseEffectFn() {
const initState = {
name: '',
age: 23,
gender: 'male',
noChild: false
}
const [param, setState] = useState(initState);
const UpdateState = part => {
setState(oldState => ({
...oldState,
...part
}))
}
// 1、组件首次:componentDidMount
useEffect(() => {
console.log('1、useEffect首次加载');
UpdateState({ name: '张无忌', age: 30 });
}, []);
// 2、组件更新:componentDidUpdate
useEffect(() => {
console.log('2、useEffect组件更新');
console.log(param, 'param');
}, [param])
// 3、如果不传入 array参数 每次都会有执行
useEffect(() => {
console.log('3.1、useEffect每次都执行,只做副作用操作,不改数据');
// 在里面修改 param 会出现死循环
// UpdateState({age:31});
return () => {
console.log('3.0、没有array,组件每次提前更新');
}
});
return (
<div>
<p>人物:{param.name}、年龄:{param.age}、性别:{param.gender === 'male' ? '男' : '女'}</p>
<p><button onClick={() => { UpdateState({ age: param.age + 1 }) }}>年龄加1</button></p>
<div>{param.noChild ? null : <EffectChild></EffectChild>}</div>
<p><button onClick={() => { UpdateState({ noChild: !param.noChild }) }}>切换子组件</button></p>
</div>
)
}
第四节 详细介绍新特性useContext
1、context就是用来更方便的的实现全局数据共享的,但是由于他并不是那么好用,所以一般会使用第三方状态管理器来实现全局数据共享
1.1、redux、dva、mobx
2、useContext(context)是针对 context 上下文对象,Hooks 提供的一个API,它接受 React.createContext()的返回值作为参数,即context对象,并返回最近的context。
3、使用 useContext 是不需要再使用Provider 和 Consumer 的 。
4、当前最近的context更新时,那么使用该context的hook将会重新渲染。
5、基本使用如下:
const Context = React.createContext({age:'18',name:'周芷若'});
const AgeComp = ()=>{
const ctx = useContext(Context);
return <div>年龄:{ctx.age}岁</div>
}
5.1、组件之间传值:props方式,将 Context 逐级传入,接收使用。
5.2、export 导出Context 后,其他组件相互import 引入 Context。
5.3、创建公共数据文件store,暴露出去多个 Context 公用数据,常用。
6、一处修改,处处共享最新。
UseContextFn.js
import React, { useContext } from 'react';
import { role1, role2, role3 } from './store';
const ChildComp1 = () => {
const ctx = useContext(role2);
return (
<div>
<p>
姓名:{ctx.name}、年龄:{ctx.age}
<button onClick={()=>{}}>修改父级组件数据</button>
</p>
</div>
)
}
const ChildComp2 = () => {
const ctx = useContext(role2);
return (
<div>
<p>姓名:{ctx.name}、年龄:{ctx.age}</p>
</div>
)
}
const ChildComp3 = () => {
const ctx = useContext(role2);
return (
<div>
<p>姓名:{ctx.name}、年龄:{ctx.age}</p>
</div>
)
}
const ChildComp4 = () => {
const ctx = useContext(role3);
return (
<div>
<p>姓名:{ctx.name}、年龄:{ctx.age} <button>修改兄弟组件公用数据</button></p>
</div>
)
}
const ChildComp5 = () => {
const ctx = useContext(role3);
return (
<div>
<p>姓名:{ctx.name}、年龄:{ctx.age}</p>
</div>
)
}
export default function UseContextFn() {
const ctx = useContext(role1);
return (
<div>
<p>父组件</p>
<p>姓名:{ctx.name}、年龄:{ctx.age} <button>修改子组件公用数据</button></p>
<p>1-子组件1</p>
<ChildComp1></ChildComp1>
<p>1-子组件2</p>
<ChildComp2></ChildComp2>
<p>1-子组件3</p>
<ChildComp3></ChildComp3>
<p>2-子组件4</p>
<ChildComp4></ChildComp4>
<p>2-子组件5</p>
<ChildComp5></ChildComp5>
</div>
)
}
store.js
import React from 'react'
export const role1 = React.createContext({
age:18,
name:'周芷若'
});
export const role2 = React.createContext({
age:23,
name:'张无忌'
});
export const role3 = React.createContext({
age:19,
name:'赵敏'
});
第五节 详细介绍新特性useReducer
1、useReducer是useState的一个增强体,可以用于处理复杂的状态管理。
2、useReducer可以完全替代useState,只是我们简单的的状态管理用useState比较易用。
useReducer设计灵感源自于redux的reducer。
3、对比一下useState和useReducer的使用:
// useState的使用方法
const [state,setSate] = useState(initState);
// useReducer的使用方法
const [sate,dispatch] = useReducer(reducer,initState,initAction);
4、useReducer的参数介绍:
4.1、reducer是一个函数,根据action状态处理并更新 state。
4.2、initState是一个初始化的 state。
4.3、initAction是 useReducer初次执行时被处理的action。
5、返回值state、dispath介绍
5.1、state状态值
5.2、dispatch是更新state的方法,它接收action作为参数。
6、useReducer只需要调用dispatch(action)方法传入action即可更新state,如下:
// dispatch 是用来更新state的,当dispatch被调用的时候,reducer方法也会被调用,
// 同时根据action传入的内容去更新 state, action是传入的一个描述操作的对象
dispatch({type:'add'});
7、reducer 是 redux 的产物,他是一个函数,主要用于处理action,然后返回最新的state,可以把reducer理解成是action 和 state 的转换器,他会根据action的描述去更新state,使用例子:
(state,action)=> NewState
8、具体例子
import React, { useReducer } from 'react'
const initState = {
count: 0
}
const reducer = (state, action) => {
// 根据 dispatch 传入的action 去更新 state
switch (action.type) {
case 'reset':
return initState;
case 'add':
return { count: state.count + 1 };
case 'reduce':
return { count: state.count - 1 };
default:
return state;
}
}
export default function UseReducerFn() {
const [state, dispatch] = useReducer(reducer, initState);
return (
<div>
<p>当前数量是{state.count}</p>
<div>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
<button onClick={() => dispatch({ type: 'add' })}>+1</button>
<button onClick={() => dispatch({ type: 'reduce' })}>-1</button>
</div>
</div>
)
}
第六节 官网额外的Hooks
一、useMemo
1、useMemo 用于性能优化,通过记忆值来避免在每个渲染上执行高开销的计算
1.1、适用于复杂的计算场景,比如复杂的列表渲染,对象深拷贝等场景
1.2、使用方法如下
const memoValue = useMemo(callback,array);
. callback 是一个函数用于处理逻辑
. array 控制useMemo重新执行的数组,array改变时才会重新执行useMemo
. useMemo的返回值是一个记忆值,是callback的返回值
2、具体使用如下
import React, {useMemo} from 'react'
const UseMemoComp = () =>{
const obj1 = {maleName:'张无忌',age:12,gender:'male'};
const obj2 = {femaleName:'赵敏',age:11,gender:'female'};
const obj3 = {thirdName:'周芷若',age:12,gender:'female'};
const memoValue = useMemo(()=>Object.assign(obj1,obj2,obj3),[obj1,obj2,obj3]);
return (
<div>
<p>人物构成{JSON.stringify(memoValue)}</p>
</div>
)
}
3、不能在useMemo里面写副作用逻辑处理,副作用的逻辑处理放在useEffect内处理。
二、useCallback
1、useCallback 和 useMemo一样,用于性能优化的
2、基本使用方法
const memoCallback = useCallback(callback,array);
. callback 是一个函数用于处理逻辑
. array 控制useMemo重新执行的数组,array改变时才会重新执行useCallback
. 跟useMemo不一样的是返回值是callback本身,需要执行以下。
3、具体使用如下
import React, {useCallback} from 'react'
const UseCallbackFn = () =>{
const obj1 = {maleName:'张无忌',age:12,gender:'male'};
const obj3 = {thirdName:'周芷若',age:12,gender:'female'};
const memoValue = useCallback(()=>Object.assign(obj1,obj3),[obj1,obj3]);
return (
<div>
<p>人物构成{JSON.stringify(memoValue())}</p>
</div>
)
}
三、useRef
1、方便我们访问操作dom
2、使用方法如下
import React, { useRef } from 'react'
const UseRefComp = () => {
const inputRef = useRef();
const getValue = () => {
console.log(inputRef.current.value, '拿到value');
}
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={getValue}>取值</button>
</div>
)
}
3、首先, 我们要实现一个需求 -- 点击 button 的时候 input 设置焦点.
createRef 和 useRef 均能实现:
从上面的例子看, createRef 和 useRef 的作用完全一样, 那为什么 react 要设计一个新的 hook ? 难道只是会了加上 use , 统一 hook 规范么?
官网的定义如下:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
换句人话说 , useRef 在 react hook 中的作用, 正如官网说的, 它像一个变量, 类似于 this , 它就像一个盒子, 你可以存放任何东西. createRef 每次渲染都会返回一个新的引用 , 而 useRef 每次都会返回相同的引用 。
4、修复useState,不能实时获取最新数据的事情!
useRef 不仅仅是用来管理 DOM ref 的,它还相当于 this , 可以存放任何变量.
useRef可以很好的解决闭包带来的不方便性.你可以在各种库中看到它的身影, 比如 react-use 中的 useInterval , usePrevious ……
值得注意的是,当 useRef 的内容发生变化时,它不会通知您。更改.current属性不会导致重新呈现。因为他一直是一个引用 .
参考链接:
四 代码
OtherHooksFn.js
import React, {useMemo, useCallback, useRef} from 'react'
const UseMemoComp = () =>{
const obj1 = {maleName:'张无忌',age:12,gender:'male'};
const obj2 = {femaleName:'赵敏',age:11,gender:'female'};
const obj3 = {thirdName:'周芷若',age:12,gender:'female'};
const memoValue = useMemo(()=>Object.assign(obj1,obj2,obj3),[obj1,obj2,obj3]);
return (
<div>
<p>人物构成{JSON.stringify(memoValue)}</p>
</div>
)
}
const UseCallbackFn = () =>{
const obj1 = {maleName:'张无忌',age:12,gender:'male'};
const obj3 = {thirdName:'周芷若',age:12,gender:'female'};
const memoValue = useCallback(()=>Object.assign(obj1,obj3),[obj1,obj3]);
return (
<div>
<p>人物构成{JSON.stringify(memoValue())}</p>
</div>
)
}
const UseRefComp = () => {
const inputRef = useRef();
const getValue = () => {
console.log(inputRef.current.value, '拿到value');
}
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={getValue}>取值</button>
</div>
)
}
export default function OtherHooksFn() {
return (
<div>
<p>useMemo使用</p>
<UseMemoComp></UseMemoComp>
<p>useCallback使用</p>
<UseCallbackFn></UseCallbackFn>
<p>useRef使用</p>
<UseRefComp></UseRefComp>
</div>
)
}
第七节 自定义一个Hooks
1、命名必须以 "use" 开头。
2、Hooks 其实是一个封装好的钩子函数提供给我们调用。
3、自定义封装时候需要特别注意性能,防止重复渲染等问题,官方的比较完美。
4、CustomHook.js
import React, { useEffect } from 'react'
// 自定义的 hooks 必须使用 "use" 开头
const useChangeTitle = title => {
useEffect(() => {
document.title = title;
}, [title]);
}
export default function CustomHook() {
useChangeTitle('自定义的hook使用!');
return (
<div>
自定义改变 document title 的 hook 使用
</div>
)
}
5、自定义的Hooks不能再次放在 callback 中。
报错如下:
Failed to compile:
React Hook "useChangeTitle" cannot be called inside a callback.
React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
第八节 Hooks使用规则
1、只能在顶层调用Hooks。
1.1、Hooks的调用尽量只在顶层作用域进行调用。
1.2、不要在循环,条件或嵌套函数中调用Hook,原因:可能无法保证每次组件渲染时都以相同的顺序调用Hook。
2、只在函数式组件调用Hooks。
React Hooks目前只支持函数组件,所以不能在class组件或者普通的函数里面调用。
3、Hooks应用场景如下:
3.1、函数式组件
3.2、自定义Hooks
4、未来版本 React Hooks 会扩展到 class组件,现阶段不行。