React Hooks 基本使用

背景

  • class 组件如果业务复杂,很难拆分和重构,很难测试;相同业务逻辑分散到各个方法中,逻辑混乱
  • 逻辑复用像HOCRender Props,不易理解,学习成本高
  • React提倡函数式编程,函数更易拆分,更易测试
  • 但是函数组件太简单,为了增强函数组件的功能,媲美class组件:
    • 函数组件没有statesetState
    • 函数组件没有生命周期

React Hooks 使用规范

  • 只能用于 React 函数组件和自定义Hook 中,其他地方不可以
  • 只能用于顶层代码,不能在循环、判断中使用Hooks
  • eslint 插件 eslint-plugin-react-hooks

基本使用

useState

  • 可用于模拟class组件的statesetState
javascript 复制代码
import React, { useState } from 'react';

function ClickCounter() {
    // 数组的解构
    // useState 是最基本的一个 Hook
    const [count, setCount] = useState(0); // 传入一个初始值

    const [name, setName] = useState('章三');

    // const arr = useState(0);
    // const count = arr[0];
    // const setCount = arr[1];

    function clickHandler() {
        setCount(count + 1);
        setName(name + '2020');
    }

    return (<div>
        <p>你点击了 {count} 次 {name}</p>
        <button onClick={clickHandler}>点击</button>
    </div>);
}

export default ClickCounter;

useEffect 模拟生命周期

  • 默认函数组件没有生命周期
  • 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
  • 使用 Effect hooks 可以把生命周期"钩"到函数组件中

useEffect 中返回函数 fn

  • useEffect 依赖[],组件销毁是执行fn,等于 componentWillUnmount
  • useEffect 无依赖或依赖[a,b],组件更新时执行 fn,即下一次执行useEffect之前,就会执行fn,无论更新或卸载
javascript 复制代码
import React, { useState, useEffect } from 'react';

function LifeCycles() {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('章三');

    // // 模拟 class 组件的 DidMount 和 DidUpdate
    // useEffect(() => {
    //     console.log('在此发送一个 ajax 请求');
    // });

    // // 模拟 class 组件的 DidMount
    // useEffect(() => {
    //     console.log('加载完了');
    // }, []) // 第二个参数是 [] (不依赖于任何 state);

    // // 模拟 class 组件的 DidUpdate
    // useEffect(() => {
    //     console.log('更新了');
    // }, [count, name]); // 第二个参数就是依赖的 state

    // 模拟 class 组件的 DidMount
    useEffect(() => {
        let timerId = window.setInterval(() => {
            console.log(Date.now());
        }, 1000);

        // 返回一个函数
        // 模拟 WillUnMount
        return () => {
            window.clearInterval(timerId);
        };
    }, []);

    function clickHandler() {
        setCount(count + 1);
        setName(name + '2020');
    }

    return (<div>
        <p>你点击了 {count} 次 {name}</p>
        <button onClick={clickHandler}>点击</button>
    </div>);
}

export default LifeCycles;

模拟 componentWillUnMount 注意事项

javascript 复制代码
import React from 'react';

class FriendStatus extends React.Component {
    constructor(props) {
        super(props);
        
        this.state = {
            status: false // 默认当前不在线
        };
    }
    
    render() {
        return (<div>
            好友 {this.props.friendId} 在线状态:{this.state.status}
        </div>);
    }
    
    componentDidMount() {
        console.log(`开始监听 ${this.props.friendId} 的在线状态`);
    }
    
    componentWillUnmount() {
        console.log(`结束监听 ${this.props.friendId} 的在线状态`);
    }
    
    // friendId 更新
    componentDidUpdate(prevProps) {
        console.log(`结束监听 ${prevProps.friendId} 在线状态`);
        console.log(`开始监听 ${this.props.friendId} 在线状态`);
    }
}

export default FriendStatus;
javascript 复制代码
import React, { useState, useEffect } from 'react';

function FriendStatus({ friendId }) {
    const [status, setStatus] = useState(false);

    // DidMount 和 DidUpdate
    useEffect(() => {
        console.log(`开始监听 ${friendId} 在线状态`);

        // 【特别注意】
        // 此处并不完全等同于 componentWillUnMount
        // props 发生变化,即更新,也会执行结束监听
        // 准确的说:返回的函数,会在下一次 effect 执行之前,被执行
        return () => {
            console.log(`结束监听 ${friendId} 在线状态`);
        };
    });

    return (<div>
        好友 {friendId} 在线状态:{status.toString()}
    </div>);
}

export default FriendStatus;

useRef

  • 获取 dom 节点
javascript 复制代码
import React, { useRef, useEffect } from 'react';

function UseRef() {
    const btnRef = useRef(null); // 初始值

    // const numRef = useRef(0);
    // numRef.current;

    useEffect(() => {
        console.log(btnRef.current); // DOM 节点
    }, []);

    return (<div>
        <button ref={btnRef}>click</button>
    </div>);
}

export default UseRef;

useContext

  • 定义一个主题,可隔层传递
javascript 复制代码
import React, { useContext } from 'react';

// 主题颜色
const themes = {
    light: {
        foreground: '#000',
        background: '#eee'
    },
    dark: {
        foreground: '#fff',
        background: '#222'
    }
};

// 创建 Context
const ThemeContext = React.createContext(themes.light); // 初始值

function ThemeButton() {
    const theme = useContext(ThemeContext);

    return (<button style={{ background: theme.background, color: theme.foreground }}>
        hello world
    </button>);
}

function Toolbar() {
    return (<div>
        <ThemeButton></ThemeButton>
    </div>);
}

function App() {
    return (<ThemeContext.Provider value={themes.dark}>
        <Toolbar></Toolbar>
    </ThemeContext.Provider>);
}

export default App;

useReducer

useReducer 和 redux 的区别:

  • useReduceruseState的代替方案,用于state 复杂变化
  • useReducer 是单个组件状态管理,组件通讯还需要props
  • redux 是全局的状态管理,多组件共享数据
javascript 复制代码
import React, { useReducer } from 'react';

const initialState = { count: 0 };

const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            return state;
    }
};

function App() {
    // 很像 const [count, setCount] = useState(0)
    const [state, dispatch] = useReducer(reducer, initialState);

    return (<div>
        count: {state.count}
        <button onClick={() => dispatch({ type: 'increment' })}>increment</button>
        <button onClick={() => dispatch({ type: 'decrement' })}>decrement</button>
    </div>);
}

export default App;

useMemo

  • React 默认会更新所有子组件
  • class 组件使用 SCUPureComponent做优化
  • Hooks 中使用useMemo,但优化的原理是相同的

使用 useMemo做性能优化:

  • useMemo 缓存值
  • 依赖不变,useMemo会取上一次缓存的值,一般用于避免复杂计算。
javascript 复制代码
import React, { useState, memo, useMemo } from 'react';

// 子组件
// function Child({ userInfo }) {
//     console.log('Child render...', userInfo);

//     return (<div>
//         <p>This is Child {userInfo.name} {userInfo.age}</p>
//     </div>);
// }

// 类似 class PureComponent ,对 props 进行浅层比较
const Child = memo(({ userInfo }) => {
    console.log('Child render...', userInfo);

    return (<div>
        <p>This is Child {userInfo.name} {userInfo.age}</p>
    </div>);
});

// 父组件
function App() {
    console.log('Parent render...');

    const [count, setCount] = useState(0);
    const [name, setName] = useState('章三');

    // const userInfo = { name, age: 20 }
    // 用 useMemo 缓存数据,有依赖
    const userInfo = useMemo(() => {
        return { name, age: 21 };
    }, [name]);

    return (<div>
        <p>
            count is {count}
            <button onClick={() => setCount(count + 1)}>click</button>
        </p>
        <Child userInfo={userInfo}></Child>
    </div>);
}

export default App;

useCallback

使用 useCallback做性能优化:

  • useCallback 缓存函数
  • 依赖不变,useCallback会取上一次缓存的函数,一般用于父组件给子组件传递函数,减少子组件的渲染次数。
javascript 复制代码
import React, { useState, memo, useMemo, useCallback } from 'react';

// 子组件,memo 相当于 PureComponent
const Child = memo(({ userInfo, onChange }) => {
    console.log('Child render...', userInfo);

    return (<div>
        <p>This is Child {userInfo.name} {userInfo.age}</p>
        <input onChange={onChange}></input>
    </div>);
});

// 父组件
function App() {
    console.log('Parent render...');

    const [count, setCount] = useState(0);
    const [name, setName] = useState('章三');

    // 用 useMemo 缓存数据
    const userInfo = useMemo(() => {
        return { name, age: 21 };
    }, [name]);

    // function onChange(e) {
    //     console.log(e.target.value);
    // }
    
    // 用 useCallback 缓存函数
    const onChange = useCallback(e => {
        console.log(e.target.value);
    }, []);

    return (<div>
        <p>
            count is {count}
            <button onClick={() => setCount(count + 1)}>click</button>
        </p>
        <Child userInfo={userInfo} onChange={onChange}></Child>
    </div>);
}

export default App;

自定义 hooks

作用:

  • 封装通用的功能
  • 开发和使用第三方 Hooks
  • 自定义 Hook 带来了无限的扩展性,解耦代码

使用:

  • 本质是一个函数,以 use 开头
  • 内部正常使用 useStateuseEffect 获取其他 Hooks
  • 自定义返回结果,格式不限
javascript 复制代码
import { useState, useEffect } from 'react';

function useMousePosition() {
    const [x, setX] = useState(0);
    const [y, setY] = useState(0);

    useEffect(() => {
        function mouseMoveHandler(event) {
            setX(event.clientX);
            setY(event.clientY);
        }

        // 绑定事件
        document.body.addEventListener('mousemove', mouseMoveHandler);

        // 解绑事件
        return () => document.body.removeEventListener('mousemove', mouseMoveHandler);
    }, []);

    return [x, y];
}

export default useMousePosition;
javascript 复制代码
import { useState, useEffect } from 'react';
import axios from 'axios';

// 封装 axios 发送网络请求的自定义 Hook
function useAxios(url) {
    const [loading, setLoading] = useState(false);
    const [data, setData] = useState();
    const [error, setError] = useState();

    useEffect(() => {
        // 利用 axios 发送网络请求
        setLoading(true);
        
        axios.get(url) // 发送一个 get 请求
            .then(res => setData(res))
            .catch(err => setError(err))
            .finally(() => setLoading(false));
    }, [url]);

    return [loading, data, error];
}

export default useAxios;

// 第三方 Hook
// https://nikgraf.github.io/react-hooks/
// https://github.com/umijs/hooks

hooks 注意事项

  • useState 初始化值只有第一次有效
  • useEffect 内部不能修改 state
  • useEffect 可能出现死循环
javascript 复制代码
// 关于 useEffect 内部不能修改 state 的问题
import { useEffect, useState } from 'react';

const HelloWorld = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(pre => pre + 1);
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return (<p>{count}</p>);
}

export default HelloWorld;

为何 hooks 要依赖于调用顺序?

javascript 复制代码
import React, { useState, useEffect } from 'react';

function Teach({ couseName }) {
    // 函数组件,纯函数,执行完即销毁
    // 所以,无论组件初始化(render)还是组件更新(re-render)
    // 都会重新执行一次这个函数,获取最新的组件
    // 这一点和 class 组件不一样

    // render: 初始化 state 的值 '张三'
    // re-render: 读取 state 的值 '张三'
    const [studentName, setStudentName] = useState('张三');

    // if (couseName) {
    //     const [studentName, setStudentName] = useState('张三');
    // }

    // render: 初始化 state 的值 '里斯'
    // re-render: 读取 state 的值 '里斯'
    const [teacherName, setTeacherName] = useState('里斯')

    // if (couseName) {
    //     useEffect(() => {
    //         // 模拟学生签到
    //         localStorage.setItem('name', studentName);
    //     });
    // }

    // render: 添加 effect 函数
    // re-render: 替换 effect 函数(内部的函数也会重新定义)
    useEffect(() => {
        // 模拟学生签到
        localStorage.setItem('name', studentName);
    });

    // render: 添加 effect 函数
    // re-render: 替换 effect 函数(内部的函数也会重新定义)
    useEffect(() => {
        // 模拟开始上课
        console.log(`${teacherName} 开始上课,学生 ${studentName}`);
    });

    return (<div>
        课程:{couseName},
        讲师:{teacherName},
        学生:{studentName}
    </div>);
}

export default Teach;

FQA

  1. 为什么会有 React hooks
  • class 组件不易拆分、不易测试、业务代码分散在各个方法中
  • 函数组件更易拆分和测试;但是函数组件没有state和生命周期,所以需要使用hooks来增强函数组件的功能
  1. React hooks 如何模拟生命周期?
  • 模拟componentDidMount - useEffect 依赖 []
  • 模拟componentDidUpdate - useEffect 无依赖,或者依赖 [a,b]
  • 模拟componentWillUnMount - useEffect 依赖 [],并返回一个函数
  1. 如何自定义hooks
  • use 开头,定义一个函数
  • 内部正常使用 useStateuseEffect 获取其他 Hooks
  • 自定义返回结果,格式不限
  1. 为什么要使用 hooks?
  • 完善函数组件的能力,函数更适合React组件
  • 组件逻辑复用,hooks表现更好
  1. hooks 组件逻辑复用的好处?
  • HOC 组件层级嵌套过多,不易渲染,不易调试; HOC 会劫持props,必须严格规范,容易出现疏漏
  • Render Props 学习成本高,不易理解;只能传递纯函数,而默认情况下纯函数功能有限
  • hooks 做组件逻辑复用,完全符合hooks原有原则,没有其他要求,易理解记忆;变量作用域明确;不会产生组件嵌套
  1. React hooks 容易遇到哪些坑?
  • useState 初始化值,只能初始化一次
  • useEffect 内部不能修改 state
  • useEffect 依赖引用类型,可能会出现死循环

React 18 新增 hooks?

useTransition

使用useTransition来降低渲染优先级, 可以指定 UI 的渲染优先级,哪些需要实时更新,哪些需要延迟更新。

useDefferdValue

允许变量延时更新,接收一个可选的延迟更新的最长时间

userId

使用 useId 可以生成客户端与服务端之间的唯一id ,并且返回一个字符串。

useSyncExternalStore

可以使 React 在并发模式下,保持自身 state 和来自 redux 的状态同步。

useInsertionEffect

useInsertionEffect 可以在布局副作用触发之前将元素插入到 DOM 中。

相关推荐
阿伟来咯~10 分钟前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端15 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱18 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai27 分钟前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨28 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
bysking1 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
aPurpleBerry3 小时前
JS常用数组方法 reduce filter find forEach
javascript