我在 React 项目中面临的挑战是提交分成多个子项的数据。 基本上,我正在开发一个食谱应用程序,并且我有子根组件 RecipeCreator。该父组件呈现一些子组件,负责收集用户的信息以创建要存储在数据库中的配方。孩子们都有不同的职责:一个负责向用户询问食谱图像,另一个负责处理食谱的“主体”,另一个负责收集所需的原料。 它们中的每一个都非常复杂,因为例如 IngredientsSelector 实现了一种根据用户选择的成分生成手风琴的逻辑,根据数据库上已有的成分实现了自动完成机制等; RecipeEditor 实现了自定义的react-quill 文本编辑器,等等。
现在,在编码和测试此类子组件之后,我不知道将所有组件组合在一起并将信息发送到数据库的最佳方法是什么。
我正在考虑三种不同的方法:
在父组件(RecipeCreator)中声明单个/多个状态,具有复杂的数据结构,应存储来自子组件的所有数据,并将状态设置器适当地传递给每个子组件。 “提交”按钮放置在父级逻辑中,因此只需从大状态对象(或多个状态)创建一个对象并调用 POST API 即可。我对此非常怀疑,因为每次子级更改其状态时,父级都会捕获更改并重新渲染,从而重新渲染所有子级。虽然我可以将孩子们包装在 React.Memo 包装器中,但我认为这不是最好的解决方案。以子RecipeEditor 的“极端情况”为例。 React-quill 输入由状态控制,每次击键最终都会触发组件和父组件的重新渲染,因为后者提供了状态设置器来获取输入的内容。
利用我已经实现的 Redux 存储:我不仅发现没有必要存储用户在存储中输入的所有信息,而且每次击键后父级重新渲染的问题仍然存在,因为它需要使用 useSelector() 钩子订阅商店的那一部分,实际上,该钩子的行为与前面讨论的通过 props 传递给子级的状态设置器非常相似。
在每个子组件中放置一个“保存更改”按钮,这样只有当用户单击每个部分(配料组件、配方编辑器组件、图像上传器组件...)中的保存按钮时,父组件才会看到编辑。虽然这可以消除不必要的重新渲染,但我不喜欢这种方法的风格,而且我不喜欢同一页面/表单中的多个“保存”按钮。
对于第三个选项,请考虑使用 refs:
export interface A {
a: string
}
export interface B {
b: string
}
export default function Parent() {
const ref1: MutableRefObject<A> = useRef({a: ""})
const ref2: MutableRefObject<B> = useRef({b: ""})
const handleConfirm = () => {
const a = ref1.current.a
const b = ref2.current.b
const ab = {a, b}
// send combined data
}
return (
<div>
<BlockA ref={ref1}/>
<BlockB ref={ref2}/>
<button onClick={handleConfirm}/>
</div>
)
}
// BlockA and BlockB are similar
export interface BlockAProps {
ref: MutableRefObject<A>
}
export default function BlockA(p: BlockAProps) {
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
p.ref.current.a = e.currentTarget.value
}
return (
<div>
<input onChange={handleChange}/>
</div>
)
}
第二个选项可以在孩子们收听商店更新而不是道具时单独渲染孩子们。这样,您将可以更细粒度地控制重新渲染相互依赖的子组件(例如,在列表中单击时突出显示配方步骤中提到的成分)。
第一个选项有点类似于 redux 解决方案(因为你将拥有 true 的单一来源)+你处理重新渲染。是的,你必须使用memo技术。 还可以尝试使用 useDeferredValue 来处理击键。
如果从长远来看扩展和维护是一个问题,我会坚持第二种解决方案。