React第五章 函数式编程之HOOK

186 阅读12分钟

第一节 隆重介绍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、现在我们可以使用 useReduceruseContext 配合使用实现复杂状态管理,不用再依赖第三方状态管理器

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属性不会导致重新呈现。因为他一直是一个引用 .

参考链接:

你不知道的 useRef

四 代码

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组件,现阶段不行。