繁体   English   中英

反应 useState 钩子与依赖

[英]React useState hook with dependency

TL;博士

为什么useState()没有dependency数组,例如:

const [state, setState] = useState<T>(initialState, [initialState]);

问题

在 React 中,我经常遇到这种情况

export function EditComponent<T>(props: {
    initialState: T,
    onSave: (value: T) => void,
}) {
    const { initialState, onSave } = props;
    const [state, setState] = useState<T>(initialState);
    useEffect(() => setState(initialState), [initialState]);
    
    function revert() {
        setState(initialState);
    }
    
    function save() {
        onSave(state);
    }
    
    return (
        ...
    )
}

我有一个提供一些数据的外部组件和一个允许用户编辑它(通过修改数据的副本)然后最终保存或恢复的内部组件。 当然,我需要内部组件对任何外部变化做出反应(也许新数据已经从网络或其他地方获取)。

我不喜欢这样做:

const [state, setState] = useState<T>(initialState);
useEffect(() => setState(initialState), [initialState]);

根据我对 React 的理解,这是在initialState更改时渲染组件两次:

  1. 首先渲染其父级的子级(由于initialState更改)。 在这个循环中, useEffect更新被添加到渲染后执行的queue中。
  2. 然后是执行useEffect更新后的第二种类型。

我需要的是useState上的dependency数组,以执行以下操作:

const [state, setState] = useState<T>(initialState, [initialState]);

看起来很简单,就像initialState在第一个渲染周期中被同步消费并变得可用一样,当依赖列表发生变化时,应再次执行此操作。

我试图自己实现它,但我想出的似乎更像是一个 hack:

import { DependencyList, Dispatch, SetStateAction, useRef, useState } from 'react';

interface MemoContext<S> {
    deps: DependencyList | undefined;
    state?: S
}

// Is dependency list equal (L327 areHookInputsEqual)
function areHookInputsEqual(a: DependencyList | undefined, b: DependencyList | undefined): boolean {
    if (!a) {
        console.error('Prev deps should not be null')
        return false;
    } else if (!b) {
        return false;
    }
    for (let i = 0; i < a.length && i < b.length; i++) {
        if (!Object.is(a[i], b[i])) {
            return false;
        }
    }
    return true;
}

export function useMemoState<S>(
    initialState: S | (() => S),
    deps?: DependencyList,
): [S, Dispatch<SetStateAction<S>>]  {

    function resetInitialState() {
        const s: S = typeof initialState === 'function' ? (initialState as any)() : initialState;
        ctx.state = s;
        ctx.deps = deps;
        return s;
    }

    const ctx = useRef<MemoContext<S>>({ deps: undefined, state: undefined }).current;
    // this is actually used just to preserve the rendering behaviour
    const [state, setState] = useState<S>(resetInitialState);

    if (!areHookInputsEqual(ctx.deps, deps)) {
        // They are different, perform the update
        resetInitialState()
    }

    function dispatch(action: SetStateAction<S>) {
        setState(prevState => {
            const s: S = typeof action === 'function' ? (action as any)(prevState) : action;
            ctx.state = s;
            ctx.deps = deps;
            return s;
        })
    }

    return [ctx.state!, dispatch];
}

老实说, React Core似乎是不应该碰的东西。 所以我想知道我是否遗漏了一些东西,以及是否有明确的原因说明不存在这样的功能。 或者也许有更好的解决方案?

一个简短的答案 - 我们不需要它=)

这是您重构的代码片段:

export function EditComponent<T>(props: {
    initialState: T,
    onSave: (value: T) => void,
}) {
    const { initialState, onSave } = props;
    const [state, setState] = useState<T>(initialState);

    if (initialState !== state){
        setState(initialState);
    }
    
    function revert() {
        setState(initialState);
    }
    
    function save() {
        onSave(state);
    }
    
    return (
        ...
    )
}

只需在渲染期间有条件地更新 state 即可。

有 2 个链接可能对您有所帮助:

https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM