React Hooks 学习

127 阅读17分钟

Hooks 的使用规则

  • 只能在函数的顶级作用域使用。

    • 顶级作用域:就是 Hooks 不能在循环、条件判断或者嵌套函数中执行,而必须是在顶层。 同时,Hooks 在组件的多次渲染之间,必须按照顺序被执行。
  • 只能在函数组件或者其他 Hooks 中使用。

    • Hooks 作为专门为函数组件设计的机制,使用的情况有两种:在函数组件内、在自定义的 Hooks 里面。

Hooks 的好处

  • 可以让 React 的组件绑定在任何可能的数据源上。这样当数据源发生变化时,组件能够自动刷新。
  • 最大的好处:使你在无需修改组件结构的情况下复用逻辑状态,简化了逻辑服用。
  • 另一个好处:有助于关注分离。
  • Hooks 解决了 Class 组件存在的一些代码冗余、难以逻辑复用的问题。

怎样让函数组件不会太过冗长?

  • 尽量将相关的逻辑做成独立的 Hooks,然后在函数组件中使用这些 Hooks,通过参数传递和返回值让 Hooks 之间完成交互。

Hooks 的本质

  • 提供了让 React 组件能够绑定到某个可变数据源的能力。

useState

作用

  • 让函数组件具有维持状态的能力。

遵循原则

  • state 中永远不要保存可以通过计算得到的值。

    • 从 props 传递过来的值。
    • 从 url L中读取到的值。
    • 从 cookie、localStorage 中读取的值。
  • state 虽然便于维护状态,但也有自己的弊端。一旦组件有自己的状态,意味着组件如果重新创建,就需要有恢复状态的过程,这通常会让组件变的更复杂。

  • useState 收集依赖,等到下次在执行的时候,根据依赖重新获取最新的状态,或者更新后的状态。

  • useState 是使用 闭包 + 链表 的形式实现的。

使用方法

  1. const [count, setCount] = useState(0);
  2. useState(initialState) 的参数是一个初始值,可以是任意类型的值;
  3. 它有两个返回值,一个 state ,以及一个用来更新 state 的函数;
  4. setState 函数用于更新 state,它接收一个新的 state 值,并将组件的一次重新渲染加入队列。在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。
  5. 如果要创建多个 state,就需要调用多个 useState。

两种使用方法的区别:

  • 通过一个新的 state 值更新当点击多次时,只有一次点击事件生效,因为 count 值是没有变化的
  • 通过函数更新返回新的 state。使用函数式更新 state 时,就不会出现点击多次,只有一次点击事件生效的情况,因为它可以获取到之前的 state 值,也就是说 prevCount 每次都是最新的值。
第一种:setCount(count + 1); 
第二种:setCount2((prevCount) => { return prevCount + 1;});

示例

比如:声明了一个名为 count 的 state,当要更新数据时,使用 setCount 进行更新数据,当调用 setCount 时,count 这个state就会更新,并触发组件的刷新。

import React, { useState } from 'react';

function Example() {
    // 创建一个保存 count 的 state,并给初始值 0
    const [count, setCount] = useState(0);
    // 定义一个年龄的 state,初始值是 42
    const [age, setAge] = useState(42);
    // 定义一个水果的 state,初始值是 banana
    const [fruit, setFruit] = useState('banana');
    // 定一个一个数组 state,初始值是包含一个 todo 的数组
    const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

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

useEffect

作用

  • 可以在函数组件中执行副作用操作。
  • 监听数据变化,变化后会执行方法,或者会执行第一个参数。
  • 通过这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数,并且在只能怪 DOM 更新之后调用它。

副作用

  • 是指一段和当前执行结果无关的代码。改变 DOM、添加订阅、设置定时器、记录日志,都是副作用。

特殊用法

  • 没有依赖项,则每次 render 后都会重新执行。
  • 每次 render 执行 先卸载、后创建 的顺序。

为什么在组件内部调用 useEffect?

  • 将 useEffect 放在组件内部让我们可以在 effect 中直接访问 state 变量。

useEffect 会在每次渲染后都执行吗?

  • 默认情况下,它在第一次渲染之后和每次更新之后都会执行。

为什么要在 effect 中返回一个函数?

  • 这是 effect 可选的清除机制,每个 effect 都可以返回一个清除函数。

React 何时清除 effect?

  • react 会在页面卸载的时候执行清除操作。
  • 通过这样一个简单的机制,我们能够更好的管理副作用,从而确保组件和副作用的一致性。
  • useEffect 当依赖发生变化时,可以异步执行里面的回调。

使用方法

  • useEffect(()=>{}, []);
  • useEffect 接收两个参数,useEffect(callback, dependencies),第一个参数为要执行的函数 callback, 第二个是可选的依赖项数组 dependencies,依赖项可接受多个参数。
  • 其中,依赖项是可选择的,如果不指定,那么 callback 就会在每次函数组件执行完之后都执行;如果指定了,那么只有依赖项的值发生变化的时候,它才会执行。
  • 对应到 Class 组件,那么 useEffect 就涵盖了 componentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期方法。
  • useEffect 是每次组件 render 完后判断依赖并执行。

示例

  • 每次 render 完一定执行:不提供第二个依赖项参数。
useEffect(() => {
    console.log('每次 render 完一定执行');
})
  • 仅第一次 render 后执行:提供一个空数组作为依赖项,只有首次执行时触发。 对应 class 组件的 componentDidMount
useEffect(() => {
    // 组件首次渲染时执行,等价于 class 组件中的 componentDidMount
    console.log("执行一次");
}, []);
  • 第一次以及依赖项发生变化后执行:提供依赖项数组。
useEffect(() => {
    console.log("依赖项发生变化时执行");
}, [id, count]);
  • 组件 unmount 后执行:返回一个回调函数,用于在组件销毁的时候做一些清理的操作。 对应 Class 组件中的 componentWillUnmount。返回的回调函数是卸载。
useEffect(() => {
    return () => {
        console.log("======模仿componentWillUnMount");
    };
}, []);
  • 自定义 hooks,模仿 componentDidUpdate
/**
* 调用 自定义 hooks 组件:useDidUpdate
*/
useDidUpdate(() => {
    console.log('模仿componentDidUpdate====')
})
    
/**
* 自定义的 hooks 组件:useDidUpdate
*/
import React, {useEffect, useRef} from 'react';
  
/**
 * 模仿 componentDidUpdate
 * @param cb 传过来的回调函数
 */
export default (cb) => {
    const isMount = useRef(true)
    useEffect(() => 
        /**
        * 初始化的时候进入该判断,然后我们把条件置为 false
        * 并且阻塞后续的程序运行
        */
        if (isMount.current) {
            isMount.current = false
            return
        }
        /**
         * 在这里调用传过来的回调函数
         */
        cb()
    })
};

useCallback

作用

  • 缓存回调函数。
  • 用来优化子组件,防止子组件的重复渲染。

使用方法

  • useCallback(fn, deps)。
  • useCallback 有两个参数,第一个参数是定义的回调函数,第二个参数是依赖的变量数组。
  • useCallback 大部分使用场景是搭配 react.memo 使用。
  • 用 useCallback 包裹的函数,根据依赖是否发生变化,才会决定是否返回一个新的函数,如果没有变化,就会返回上一次缓存的函数。 这样就保证了组件不会创建重复的回调函数,而接收这个回调函数作为属性的组件,也不会频繁的需要重新渲染。
  • 只有当依赖项发生变化时,才会重新创建回调函数。

第二个参数

第二个参数何时生成新函数
组件首次执行及更新都会生成新函数
[] 空数组组件首次执行生成,之后不变
[count,id] 变量数组组件首次执行、依赖变化时生成新函数

示例

import React, {memo, useCallback, useState} from 'react';
​
/**
 * React.memo 这个方法,会对 props 做一个浅层比较,如果 props 没有发生改变,则不会重新渲染此组件。
 * 没有用 useCallback 包裹的函数,每次都会重新声明一个方法,新方法虽然和旧方法一样,但是依旧是两个不同的对象,
 * React.memo 对比后发现 props 改变,就会重新渲染。
 * @type {React.NamedExoticComponent<{readonly onClick?: *, readonly children?: *}>}
 */
const Button = memo(({onClick, children}) => {
    return (
        <>
            <button onClick={onClick}>{children}</button>
            <span>{Math.random()}</span>
        </>
    );
});

const HooksUseCallback = () => {

    const [count1, setCount1] = useState(0);
    const [count2, setCount2] = useState(0);
    const [count3, setCount3] = useState(0);
    /**
     * Button1 事件:只会更新 Button1 和 Button3 后面的内容
     * 只要父组件更新后都会更新
    */
    const handleClickButton1 = () => {
        setCount1(count1 + 1)
    };
    /**
     * Button2 事件:会将三个按钮后的内容都更新
     * 经过 useCallback 优化后的 Button2 是点击自身时才会更新
     * @type {(function(): void)|*}
    */
    const handleClickButton2 = useCallback(() => {
        setCount2(count2 + 1);
    }, [count2]);

    return (
        <div>
            <Button onClick={handleClickButton1}>Button1</Button>
            <Button onClick={handleClickButton2}>Button2</Button>
            {/* Button3 事件:也是只更新 Button1 和 Button3 后面的内容 */}
            {/* 只要父组件更新后都会更新 */}
            <Button onClick={() => {setCount3(count3 + 1);}}>Button3</Button>
    </div>
  )
};
​
export default HooksUseCallback;

useMemo

作用

  • 缓存函数的计算结果。
  • 可以优化当前组件也可以优化子组件,优化当前组件主要是通过 memoize 来将一些复杂的计算逻辑进行缓存

使用方法

  • useMemo(fn, deps)。
  • 接收两个参数。第一个参数 fn 是产生所需数据的一个计算函数, 第二个参数是依赖项。
  • 返回一个 memoized 值, 只有当它的某个依赖项改变时才重新计算 memoized 值,初始化的时候也会调用一次,这种优化有助于避免在每次渲染时都进行高开销的计算。
  • 创建一个函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生变化时,才会重新调用此函数,返回一个新的值。

第二个参数

第二个参数何时生成新函数
组件首次执行及更新都会生成新函数
[] 空数组组件首次执行生成,之后不变
[count,id] 变量数组组件首次执行、依赖变化时生成新函数

useMemo 的好处

  • 避免重复计算。
  • 避免子组件的重复渲染。

使用 useMemo 实现 useCallback

  • useCallback 和 useMemo 从本质上来说,它们只是做了同一件事情:建立了一个绑定某个结果到依赖数据的关系。只有当一来变了,这个结果才需要被重新得到。
const myEventHandler = useMemo(() => {
    // 返回一个函数作为缓存结果
    return () => {
        // 在这里进行事件处理
    }
}, [dep1, dep2]);

示例

// 使用 userMemo 缓存计算的结果
const usersToShow = useMemo(() => {
    if (!users) return null;
    return users.data.filter((user) => {
        return user.first_name.includes(searchKey));
    }
}, [users, searchKey]);

使用 useMemo 示例

import React, {useMemo, useState} from 'react';

const LearnUsememo = () => {
    
    const [num, setNum] = useState(1);
    const [num1, setNum1] = useState(1);
    /**
     * 第一个参数:一个函数
     * 第二个参数:依赖项
     *
     * 如果依赖项传空的话,只会在初始化的时候调用一次
     * 调用的是第一个函数,然后拿他的返回值,也就是说
     * 下面的 count 就是返回值
     *
     * 注意:如果 useMemo 里面使用了,相关的 useState 或者 props 里面的值
     * 进行计算,那么就需要注意依赖项要传入这个值
     * 不然,计算结果可能都是老值
     * @type {number}
     */
    const count = useMemo(() => {
        console.log('我是调用了')
        return num * 10
    }, [num]);

    return (
        <div>
            <div>useMemo</div>
            {count}
            <button onClick={() => setNum(num + 1)}>+1</button>
            {num} - {num1}
            <button onClick={() => setNum1(num1 + 1)}>+1</button>
        </div>
    );
};
​
export default LearnUsememo;

useRef

作用

  • 在多次渲染之间共享数据。
  • 用来获取 DOM 元素对象。
  • 保存数据。
  • useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

好处

  • 保存的数据不会因为组件的更新而丢失。

使用方法

  • const myRefContainer = useRef(initialValue);
import React, {useEffect, useRef, useState} from 'react';
    
const HooksUseRef = () => {
    const [count, setCount] = useState(0)
    const timerId = useRef();
    useEffect(() => {
        timerId.current = setInterval(() => {
            setCount(e => e + 1)
    }, 1000)
}, [])

    const stop = () => {
        clearInterval(timerId.current)
    }

    return (
        <div>
            {count}
            <button onClick={stop}>停止</button>
        </div>
    );
};

export default HooksUseRef;

示例

// 子组件
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import { Modal } from 'antd';
import MatrixTextArea from '@/components/Matrix/MatrixTextArea';
​
// React 传递 ref 给 forwardRef 内函数 (props, ref) => ...,作为其第二个参数。
// 第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
const CancelComponent = (props, ref) => {
    const { title, handleSubmit, form, id = 'id', placeholder = '请输入',required='false' } = props;
    const [visible, setVisible] = useState(false);
    const [confirmLoading,setConfirmLoading] = useState(false)
    const statusRef = useRef(true);
​
    // 可以让你在使用 ref 时自定义暴露给父组件的实例值,搭配 forwardRef 使用
    useImperativeHandle(ref, () => ({
        showHidden: (vis) => {
            setVisible(vis);
        },
        showLoading:(vis) => {
            setConfirmLoading(vis);
        },
    }));
​
    const onOk = () => {
        form.validateFields([id], (errors, values) => {
            if (errors) return;
            if (handleSubmit) handleSubmit(values);
        });
    };
​
    return (
        <Modal
        visible={visible}
        title={title}
        confirmLoading={confirmLoading}
        onOk={onOk}
        onCancel={() => setVisible(false)}
        maskClosable={false}
        destroyOnClose
        ref={statusRef}
        >
            <MatrixTextArea id={id} form={form} md={0} placeholder={placeholder} required={required} maxLength={300} style={{ minHeight: 80 }} />
        </Modal>
    );
};

export default forwardRef(CancelComponent);
// 父组件函数式组件调用:
const childRef = useRef(null);
const handleSubmit = async (e) => {
    childRef.current.showLoading(true);
    dispatch({ type: 'carInStore/fetchCancelStore',payload: { ...e, id: stockData.id },}).then(() => {
        childRef.current.showHidden(false); // 调用子组件的 弹窗按钮显示隐藏
        childRef.current.showLoading(false);// 调用子组件的 弹窗loading显示隐藏
        handleSearch(param);
    });
};
<CancelComponent title="取消入库" placeholder="请输入取消入库原因" id="cancelReason" handleSubmit={handleSubmit} required ref={childRef} form={form} />

// 父组件类式组件调用:
handleSubmit = (values) => {
    const { rowData, params } = this.state;
    const data = { ...values, ids: rowData.id, type: 3, flag: 2,};
    const { dispatch } = this.props;
    const refresh = this.handleSearch;
    this.child.showLoading(true);
    dispatch({ type: 'receipt/confirm', payload: data,}).then(() => {
        refresh(params);
        this.child.showHidden(false); // 调用子组件的 弹窗按钮显示隐藏
        this.child.showLoading(false);// 调用子组件的 弹窗loading显示隐藏
    });
};
<CancelComponent title="驳回原因" placeholder="请输入驳回原因" id="reason" handleSubmit={this.handleSubmit} required ref={ref => this.child = ref} form={form} />

useImperativeHandle

作用

  • 可以让你在使用 ref 时自定义暴露给父组件的实例值。

使用方法

  • useImperativeHandle 应当与 forwardRef 一起使用。

  • useImperativeHandle(ref,()=>{})。接收两个参数,第一个参数:父组件传递的 ref 属性,第二个参数:返回一个对象,以供给父组件中通过 ref.current 调用该对象的方法。

  • ref 和 forwardRef 的结合使用:

    • forwardRef:通过 forwardRef 可以将 ref 转发给子组件。

      [Refs]  zh-hans.reactjs.org/docs/forwar… 

    • 子组件拿到父组件创建的 ref ,绑定到自己的某一个元素中。

示例

function FancyInput(props, ref) {
    const inputRef = useRef();
    useImperativeHandle(ref, () => ({
        focus: () => {
            inputRef.current.focus();
        }
    }));
    return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

useContext

React 提供了 Context 这样一个机制,能够让所有在某个组件开始的组件树上创建一个 Context。这样这个组件树上的所有组件,就都能访问和修改这个 Context。

作用

  • 定义全局状态,进行全局状态管理。
  • 为了能够进行数据的绑定。 当这个 Context 的数据发生变化时,使用这个数据的组件就能够自动刷新。
  • Context 更多的是提供了一个强大的机制,让 React 应用具备定义全局的响应式的能力。
  • 可以帮助我们跨越组件层级直接传递变量,实现数据共享。一般用于祖孙传值。一般用于统一下发,权限,当前所在地,主题色等。
  • Context的作用就是对它所包含的组件树提供全局共享数据的一种技术。

使用方法

  • const MyContext = React.createContext(initialValue);一个 Context 是从某个组件为根组件的组件树上可用的, 所以,我们需要有 API 能够创建一个 Context,这就是 React.createContext API。
  • const value = useContext(MyContext); 组件的 API 签名。
  • 使用 useContext 在改变一个数据时,是通过自己逐级查找对比改变的数据,进行渲染,而不是通过数据响应式来监控变量的。

示例

import React, {createContext, useContext, useState} from 'react';
// 创建 Context
const CountContext = createContext(0)

// 孙子组件
const Grandson = () => {
    const {count, setCount} = useContext(CountContext)
    console.log(useContext(CountContext))
    return (
        <div>
            {count}
            {/* 在孙子组件中,使用爷爷组件定义的变量 count,并进行 +1 操作 */}
            <button onClick={() => setCount(e => e + 1)}>孙子点击事件</button>
        </div>
    )
}

// 子组件
const Child = () => {
    // 通过 useContext 把刚刚创建好的 CountContext 作为参数传进去,并读取 count 值
    const {count, setCount} = useContext(CountContext)
    return (
        <div>
            {count}
            {/* 在子组件中,使用父组件定义的变量 count,并进行 -1 操作 */}
            <button onClick={() => setCount(e => e - 1)}>子组件点击事件</button>
            <Grandson />
        </div>
    )
}

// 父组件
const HooksUseContext = () => {
    const [count, setCount] = useState(0)
    return (
        <div>
            {count}
            <button onClick={() => setCount(e => e + 1)}>点击</button>
            {/* 使用 CountContext.Provider 包裹需要接收参数的子组件,并通过 value 传值 */}
            <CountContext.Provider value={{count, setCount}}>
                <Child />
            </CountContext.Provider>
        </div>
    );
};
​
export default HooksUseContext;

useReducer

作用

  • useState 的替代方案。

使用方法

  • const [state, dispatch] = useReducer(reducer, initialArg, init);
  • useReducer 接收两个参数:第一个参数是一个 reducer 函数;第二个参数是初始化的 state,返回值为最新的 state 和 dispatch 函数(用来触发 reducer 函数,计算对应的 state。)
  • reducer 本质是一个纯函数,没有任何 UI 和副作用,这意味着相同的输入(state,action),reducer 函数无论执行多少遍,始终会返回相同的输出。
  • useState的替代方案,它接收一个形如 ( state , action ) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
  • 在某些场景下,useReducer 会比 useState 更适用,例如:state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数。
  • 这一次彻底搞定useReducer-使用篇 - 知乎 (zhihu.com)

示例

import React, {useReducer} from 'react';

const HooksUseReducer = () => {
    // 第一个参数:应用的初始化
    const initialState = {count: 0}
    /**
     * 第二个参数:state 的 reducer 处理函数
     * @param state 默认初始值
     * @param action 传递的事件名称
     * @returns {{count: *}|{count: number}}
    */
    const reducer = (state, action) => {
        console.log(state, action, '===')
        switch (action.type) {
        case 'decrement':
            return {count: state.count - 1}
        case 'increment':
            return {count: state.count + 1}
        default:
            throw new Error()
        }
    }
    /**
     * 返回值:最新的 state 和 dispatch 函数
     * state:初始值
     * dispatch:函数
     */
    const [state, dispatch] = useReducer(reducer, initialState)
    console.log(state)
    return (
        <div>
            {/* useReducer 会根据 dispatch 的 action,返回最终的 state,并触发 reducer */}
            count:{state.count}
            {/* dispatch 用来接收一个 action 参数 [reducer 中的 action],用来触发 reducer 函数,更新最新的状态 */}
            <button onClick={() => dispatch({type: 'decrement'})}>点击-1</button>
            <button onClick={() => dispatch({type: 'increment'})}>点击+1</button>
        </div>
    );
};

export default HooksUseReducer;

React.memo

使用方法

  • 高阶组件定义:参数为组件,返回值为新组件的函数。

  • 默认会对 props 做一层浅比较,如果 props 不变,那么就不会重新渲染。

  • 可接收第二个参数:

    • 默认情况下其只会对复杂对象做浅层对比, 如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。第二个参数是一个函数,返回 true 不渲染,false 渲染。
    • 可以做渲染劫持,自定义逻辑做劫持。

特点

  • React.memo 仅检查 props 变更。 如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。

示例

import React, {memo, useState} from 'react';
​
/**
 * 只有点击按钮一时,组件才会渲染
 * 因为,经过 memo 高阶组件包裹的函数,只有 props 发生变化时,才会更新
 * Child 组件接收的是 count 值,一旦 count 的值,有所改变,便会重新渲染组件
 * 而 count2 未传给 Child 组件值,所以,当 count2 发生改变时,不会引起 Child 组件的渲染
 * @type {React.NamedExoticComponent<object>}
 */
const Child = memo(() => {
    console.log('Child组件更新啦')
    return <div>Child</div>
})

/**
 * 未经 memo 组件包裹的组件,只要父组件发生变化,但是子组件数据没有发生变化,也会重新渲染组件
 * @returns {JSX.Element}
 * @constructor
 */
const GrandSon = () => {
    console.log('GrandSon组件更新啦')
    return <div>GrandSon</div>
}

const HooksReactMemo = () => {

    const [count, setCount] = useState(0)
    const [count2, setCount2] = useState(0)

    return (
        <div>
            count:{count}
            <button onClick={() => setCount(e => e + 1)}>点击事件</button>
            {count2}
            <button onClick={() => setCount2(e => e + 1)}>点击事件</button>
            <GrandSon count={count} />
            <Child count={count} />
        </div>
    );
};

export default HooksReactMemo;

劫持渲染

父组件:
import React, {useState} from 'react';
import List from "./list";
​
const Homework = () => {
    const [cards, setCards] = useState([{name: '測試'}])
    const add = () => {
        setCards([...cards, {name: '测试'}])
    }
    return (
        <div>
            <button onClick={add}>添加</button>
            <List cards={cards}/>
        </div>
    );
};
​
export default Homework;
​
子组件:
import React from 'react';
import './index.css'const List = ({cards}) => {
    return (
        <div className={'card'}>
            {
                cards.map((item, index) => <div>{item.name} -- {index + 1}</div>)
            }
        </div>
    );
};
​
export default React.memo(List, (preProps, newProps) => {
    /**
     * prevProps:旧的值
     * nextProps:新的值
     * 如果把 nextProps 传入 render 方法的返回结果与
     * 将 prevProps 传入 render 方法的返回结果一致则返回 true,
     * 否则返回 false
     * 每次渲染前,或者说,每次 props 更新后,要渲染页面前,
     * 在这里做一层拦截
     */
    return newProps.cards.length > 20 && newProps.cards.length < 30
});

hooks 使用 form

  • 经过 Form.create() 包装过的组件自带 form 属性,使用 connect 连接 model 层。
function mapStateToProps({ carInStore, loading }) {
    return {
        data: carInStore,
        loading: loading.effects['carInStore/fetchList'],
    };
}
const Index = (props) => {
    const { form, loading, dispatch, data: {dataList, showConfirmModal } } = props;
    return (
        <div> hooks 使用 form </div>
  );
};

export default Form.create()(connect(mapStateToProps)(Index));

自定义 hooks

使用方法

  • 声明一个以 use 开头的函数,比如:useCounter。

与普通函数的区别

  • 在语义上是有区别的,区别就在于函数中有没有用到其它的 Hooks。
  • 就是说,如果你创建了一个 useXXX 的函数,但是,内部并没有用到任何其它的 hooks,name这个函数就不是一个 Hook,而是一个普通函数。但是如果用了其它 Hooks,那么它就是一个 Hook。

原则

  • 名字一定是以 use 开头的函数, 不遵循这个约定的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。
  • 函数内部一定调用了其他的 Hooks,可以是内置的 Hooks,也可以是其他自定义的 Hooks。 这样才能够让组件去刷新,或者去产生副作用。

优势

  • 一方面能让这个逻辑得到重用,另一个方面也能让代码更加语义化,并且易于理解和维护。
  • 自定义 hook:
import { useState, useCallback }from 'react';
 
function useCounter() {
    // 定义 count 这个 state 用于保存当前数值
    const [count, setCount] = useState(0);
    // 实现加 1 的操作
    const increment = useCallback(() => setCount(count + 1), [count]);
    // 实现减 1 的操作
    const decrement = useCallback(() => setCount(count - 1), [count]);
    // 重置计数器
    const reset = useCallback(() => setCount(0), []);
  
    // 将业务逻辑的操作 export 出去供调用者使用
    return { count, increment, decrement, reset };
}
  • 在组件中使用自定义 hook
import React from 'react';

function Counter() {
    // 调用自定义 Hook
    const { count, increment, decrement, reset } = useCounter();

    // 渲染 UI
    return (
        <div>
            <button onClick={decrement}> - </button>
            <p>{count}</p>
            <button onClick={increment}> + </button>
            <button onClick={reset}> reset </button>
        </div>
    );
}