React:如何在功能组件中创建状态相关的功能?
React: How to create state dependent function in functional components?
在我的应用程序中,我有这样的组件:
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>
);
}
在函数 setValue
和 getValue
中我总是在 inputValue
中有默认值 - 我无法在这个函数中获得更新状态。我如何组织我的代码来解决这个问题?
P. S.
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 函数 setValue
和 getValue
在第一次渲染时不可用:
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]);
试试这个:
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) );
}
}
}, []);
写custom hooks to extract your logic into separate units of code. Since your state changes rely in part on the previous state, you should call 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]);
};
由于 getValue
、setValue
和 ref
已被记忆,因此唯一实际更改的依赖项是 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>
);
};
在我的应用程序中,我有这样的组件:
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>
);
}
在函数 setValue
和 getValue
中我总是在 inputValue
中有默认值 - 我无法在这个函数中获得更新状态。我如何组织我的代码来解决这个问题?
P. S.
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 函数 setValue
和 getValue
在第一次渲染时不可用:
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]);
试试这个:
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) );
}
}
}, []);
写custom hooks to extract your logic into separate units of code. Since your state changes rely in part on the previous state, you should call 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]);
};
由于 getValue
、setValue
和 ref
已被记忆,因此唯一实际更改的依赖项是 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>
);
};