【问题标题】:React: How to create state dependent function in functional components?React:如何在功能组件中创建状态依赖函数?
【发布时间】:2020-04-13 03:48:01
【问题描述】:

在我的应用程序中,我有这样的组件:

const MyComponent = props => {

    const { attrOneDefault, attrTwoDefault, formControl } = props;  
    const [inputValue, setInputValue] = useState({
        attr_one: attrOneDefault,
        attr_two: attrTwoDefault
    });

    const getValue = ( attr ) => {
        return inputValue[attr];
    }
    const setValue = ( attr, val ) => {
        if( attr === 'attr_one' ) {
            if( val === 'bar' && getValue(attr) !== 'foo' ) {
                val = 'foo bar';
            }
        }
        setInputValue( {...inputValue, [attr]: val} );
    }

    useEffect( () => {
        if( formControl ) {         
            Object.keys(inputValue).forEach( attribute => {
                formControl.subscribeToValueCollecting( attribute, () => {
                    return getValue(attribute);
                });
                formControl.subscribeToValueChange( attribute, ( value ) => {
                    setValue( attribute, value );
                    return true;
                });
            });
        }

        return () => { 
            if( formControl ) {
                Object.keys(inputValue).forEach( attribute => formControl.unsubscribe(attribute) );
            }
        }
    }, []);

    return (
        <div class="form-field">
            <input
                type="text"
                value={getValue('attr_one')}
                onChange={ e => setValue('attr_one', e.target.value)}
            />
            <input
                type="checkbox"
                checked={getValue('attr_two')}
                onChange={ e => setValue('attr_two', !!e.target.checked)}
            />
        </div>
    );
}

setValuegetValue 函数内部我总是在inputValue 中有默认值 - 我无法在这些函数内部获取更新状态。我如何组织我的代码来解决这个问题?

P。

1) 使用 useCallback 我得到相同的结果:

const getValue = useCallback( ( attr ) => {
    return inputValue[attr];
}, [inputValue]);
const setValue = useCallback( ( attr, val ) => {
    if( attr === 'attr_one' ) {
        if( val === 'bar' && getValue(attr) !== 'foo' ) {
            val = 'foo bar';
        }
    }
    setInputValue( {...inputValue, [attr]: val} );
}, [inputValue]);

2) 使用 useEffect 函数 setValuegetValue 在第一次渲染时不可用:

let getValue, setValue;
useEffect( () => {
    getValue = ( attr ) => {
        return inputValue[attr];
    }
    setValue = ( attr, val ) => {
        if( attr === 'attr_one' ) {
            if( val === 'bar' && getValue(attr) !== 'foo' ) {
                val = 'foo bar';
            }
        }
        setInputValue( {...inputValue, [attr]: val} );
    }
}, [inputValue]);

【问题讨论】:

  • 为什么你的setValue() 里面有看似随机的逻辑?
  • 这只是一个例子——这个函数有一些逻辑。我想让代码更短更容易理解。
  • 你的useEffect 正在捕获你的inputValue,这就是为什么它总是一样的。尝试将inputValue 传递到您的使用效果数组[]
  • 对 formControl.subscribe... 监听器使用 Ref 回调

标签: javascript reactjs functional-programming react-functional-component


【解决方案1】:

试试这个:

const getValue = ( attr ) => {
        return inputValue[attr];
    }
const getValueRef = useRef(getValue)
const setValue = ( attr, val ) => {
        setInputValue( inputValue =>{
            if( attr === 'attr_one' ) {
                if( val === 'bar' && inputValue[attr] !== 'foo' ) {
                    val = 'foo bar';
                }
            }
            return {...inputValue, [attr]: val} );
        }
}

useEffect(()=>{
    getValueRef.current=getValue
})

    useEffect( () => {
        const getCurrentValue = (attr)=>getValueRef.current(attr)
        if( formControl ) {         
            Object.keys(inputValue).forEach( attribute => {
                formControl.subscribeToValueCollecting( attribute, () => {
                    return getCurrentValue(attribute);
                });
                formControl.subscribeToValueChange( attribute, ( value ) => {
                    setValue( attribute, value );
                    return true;
                });
            });
        }

        return () => { 
            if( formControl ) {
                Object.keys(inputValue).forEach( attribute => formControl.unsubscribe(attribute) );
            }
        }
    }, []);

【讨论】:

    【解决方案2】:

    custom hooks 将您的逻辑提取到单独的代码单元中。由于您的状态更改部分依赖于先前的状态,因此您应该调用 useReducer() 而不是 useState() 以使实现更容易并且状态更改是原子的:

    const useAccessors = initialState => {
      const [state, dispatch] = useReducer((prev, [attr, val]) => {
        if (attr === 'attr_one') {
          if (val === 'bar' && getValue(attr) !== 'foo') {
            val = 'foo bar';
          }
        }
    
        return { ...prev, [attr]: val };
      }, initialState);
      const ref = useRef(state);
    
      useEffect(() => {
        ref.current = state;
      }, [ref]);
    
      const getValue = useCallback(
        attr => ref.current[attr],
        [ref]
      );
      const setValue = useCallback((attr, val) => {
        dispatch([attr, val]);
      }, [dispatch]);
    
      return { getValue, setValue, ref };
    };
    

    现在您的 useEffect() 正在省略第二个参数中的依赖项。这往往会导致您目前遇到的问题。我们可以使用useRef() 来解决这个问题。

    让我们将您的 useEffect() 也移动到自定义钩子中并修复它:

    const useFormControl = (formControl, { getValue, setValue, ref }) => {
      useEffect(() => {
        if (formControl) {
          const keys = Object.keys(ref.current);
    
          keys.forEach(attribute => {
            formControl.subscribeToValueCollecting(attribute, () => {
              return getValue(attribute);
            });
            formControl.subscribeToValueChange(attribute, value => {
              setValue(attribute, value);
              return true;
            });
          });
    
          return () => {
            keys.forEach(attribute => {
              formControl.unsubscribe(attribute);
            });
          };
        }
      }, [formControl, getValue, setValue, ref]);
    };
    

    由于 getValuesetValueref 已被记忆,唯一真正改变的依赖是 formControl,这很好。

    将所有这些放在一起,我们得到:

    const MyComponent = props =>
      const { attrOneDefault, attrTwoDefault, formControl } = props;
    
      const { getValue, setValue, ref } = useAccessors({
        attr_one: attrOneDefault,
        attr_two: attrTwoDefault
      });
    
      useFormControl(formControl, { getValue, setValue, ref });
    
      return (
        <div class="form-field">
          <input
            type="text"
            value={getValue('attr_one')}
            onChange={e => setValue('attr_one', e.target.value)}
          />
          <input
            type="checkbox"
            checked={getValue('attr_two')}
            onChange={e => setValue('attr_two', e.target.checked)}
          />
        </div>
      );
    };
    

    【讨论】:

    • @AlexShul 我不得不问,你实现了那些subscribeToValueCollecting()subscribeToValueChange() 方法吗?如果不是,它们的真正名称是什么,它们来自哪个库?这可能会帮助我避免改变对象以实现合理的性能。
    • 是的,我在自定义钩子useFormControl中实现了这个方法。这个钩子有助于验证表单和提交数据。它是从父组件传递过来的。挂钩代码:github.com/alex-shul/custom-components/blob/master/hook/Form.js
    • 欢迎任何关于性能改进的建议或提交。
    • @AlexShul 我知道已经很久了,但我最近才再次看到这个答案,并认为它值得更新。没有必要为了获得良好的性能而改变你的状态,这就是 useRef() 的用途。
    猜你喜欢
    • 2017-01-21
    • 1970-01-01
    • 2017-07-05
    • 1970-01-01
    • 2020-08-12
    • 2018-12-06
    • 1970-01-01
    • 2021-10-07
    • 2021-12-25
    相关资源
    最近更新 更多