react 父子通讯如何解耦
前言
提到父子组件通讯,我们自然而然的就想到了下一种办法:将需要沟通的状态定义到父组件上,然后在将 状态和设置状态都传入子组件!
import { useState } from 'react';
const HeadForm = (props) => {
const [motto, setMotto] = useState('');
return (
<div className="head-form">
<div className="form-groups">
<div className="form-group flex gap-1">
<label htmlFor="name">姓名</label>
<input className="border" type="text" value={props.name} onChange={(e) => props.setName(e.target.value)} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">年龄</label>
<input className="border" type="text" value={props.age} onChange={(e) => props.setAge(e.target.value)} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">签名</label>
<input className="border" type="text" value={motto} onChange={(e) => setMotto(e.target.value)} />
</div>
</div>
</div>
);
};
const DemoTwo = () => {
const [name, setName] = useState('');
const [age, setAge] = useState('');
return (
<div className="wrapper">
<HeadForm name={name} age={age} setName={setName} setAge={setAge} />
<div>{name}{age}</div>
</div>
);
};
export default DemoTwo;
然而这样我觉得并不好,从设计和落上来看都明明属于子组件的内容,
但是为了便于父子沟通,硬生生把这内容(状态定义)写到了父组件中!
这反而让两个组件有了一定的耦合度,子组件没有那么独立!
我想要的是,子组件是否可以独立运行?哪怕父组件不传递任何参数也能不报错!
方式1:通知父组件
将状态都定义到子组件中,通过监听子组件这些需要传递状态的变化,通知到父组件 props.onChange(searForm)
// 父子组件解耦,父组件实时更新与子组件关联的状态
import { FC, useEffect, useMemo, useState } from 'react';
interface SearForm {
name: string;
age: string;
}
interface HeadFormProps {
onChange: (values: SearForm) => void;
}
const HeadForm: FC<HeadFormProps> = (props) => {
const [name, setName] = useState('');
const [age, setAge] = useState('');
const [motto, setMotto] = useState('');
// 使用 useMemo 计算一个依赖于 name 和 age 的值(仅收集外部组件所需的值)
const searForm = useMemo<SearForm>(() => {
const values = { name, age };
return values;
}, [name, age]); // 依赖项
// 只要searForm变更,就通知父组件更新
useEffect(() => {
props.onChange(searForm);
}, [props, searForm]);
return (
<div className="head-form">
<div className="form-groups">
<div className="form-group flex gap-1">
<label htmlFor="name">姓名</label>
<input className="border" type="text" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">年龄</label>
<input className="border" type="text" value={age} onChange={(e) => setAge(e.target.value)} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">签名</label>
<input className="border" type="text" value={motto} onChange={(e) => setMotto(e.target.value)} />
</div>
</div>
</div>
);
};
const DemoTwo = () => {
const [searForm, setSearForm] = useState<SearForm>({ name: '', age: '' });
const onFormChange = (values) => setSearForm(values);
const onSubmit = () => {
console.log(searForm);
};
return (
<div className="wrapper">
<HeadForm onChange={onFormChange} />
<div>{JSON.stringify(searForm)}</div>
<div className="mt-2">
<button className="bg-blue-200 px-2 rounded cursor-pointer" onClick={onSubmit}>
提交
</button>
</div>
</div>
);
};
export default DemoTwo;
上边代码中,我用到了 useMemo来统一收集需要和父组件通信的值,你也可以直接仅用 useEffect
useEffect(() => {
props.onChange({ name, age });
}, [props, name, age]);
方式2:通知父组件
方式 2 和方式 1 本质上是一样的,只不过是不在使用 useEffect 或 useMemo来通知,毕竟 useEffect 这个钩子用不好会产生性能问题 (虽然 react 也出了 React Compiler 来优化这些问题)!
下边我是借助于 setXX 来进行设置
import { FC, useState } from 'react';
interface SearForm {
name: string;
age: string;
}
interface HeadFormProps {
onChange: (values: SearForm) => void;
}
const HeadForm: FC<HeadFormProps> = (props) => {
const [name, _setName] = useState('');
const [age, _setAge] = useState('');
const [motto, setMotto] = useState('');
const setName = (value) => {
_setName(value);
props.onChange({ name: value, age });
};
const setAge = (value) => {
_setAge(value);
props.onChange({ name, age: value });
};
return (
<div className="head-form">
<div className="form-groups">
<div className="form-group flex gap-1">
<label htmlFor="name">姓名</label>
<input className="border" type="text" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">年龄</label>
<input className="border" type="text" value={age} onChange={(e) => setAge(e.target.value)} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">签名</label>
<input className="border" type="text" value={motto} onChange={(e) => setMotto(e.target.value)} />
</div>
</div>
</div>
);
};
const DemoTwo = () => {
const [searForm, setSearForm] = useState<SearForm>({ name: '', age: '' });
const onFormChange = (values) => setSearForm(values);
const onSubmit = () => {
console.log(searForm);
};
return (
<div className="wrapper">
<HeadForm onChange={onFormChange} />
<div>{JSON.stringify(searForm)}</div>
<div className="mt-2">
<button className="bg-blue-200 px-2 rounded cursor-pointer" onClick={onSubmit}>
提交
</button>
</div>
</div>
);
};
export default DemoTwo;
出此之外,我曾经还试图借助于 setXX,直接通知父组件,结果报错:执行setName 的时候,触发组件 立即render,然后在这个过程中 props.onChange 又开始执行,结果就犯了 render 期间禁止更新了状态的禁忌(即React 更新状态应该是纯的,不允许在渲染过程中再次触发其他组件的状态更新,因为这会导致渲染过程变得不可预测)。 以下代码是错误的
const [name, setName] = useState('');
const onNameChange = (e) => {
setName(() => {
props.onChange({ name: e.target.value, age });
return e.target.value;
});
};
<input className="border" type="text" value={name} onChange={onNameChange} />
方式3:ahooks
使用三方库 ahooks,模拟出 vue 的 v-model或.sync的效果
import { FC, useState } from 'react';
import { useControllableValue } from 'ahooks';
interface SearForm {
name: string;
age: string;
}
interface HeadFormProps {
value: SearForm;
onChange: (values: SearForm) => void;
}
const HeadForm: FC<HeadFormProps> = (props) => {
const [state, setState] = useControllableValue<SearForm>(props, {
defaultValue: { // 父组件的参数会覆盖这个默认值
name: '',
age: '',
},
});
const [motto, setMotto] = useState('');
return (
<div className="head-form">
<div className="form-groups">
<div className="form-group flex gap-1">
<label htmlFor="name">姓名</label>
<input className="border" type="text" value={state.name} onChange={(e) => setState({...state, name: e.target.value})} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">年龄</label>
<input className="border" type="text" value={state.age} onChange={(e) => setState({...state, age: e.target.value})} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">签名</label>
<input className="border" type="text" value={motto} onChange={(e) => setMotto(e.target.value)} />
</div>
</div>
</div>
);
};
const DemoTwo = () => {
const [searForm, setSearForm] = useState<SearForm>({ name: '', age: '' });
const onSubmit = () => {
console.log(searForm);
};
return (
<div className="wrapper">
<HeadForm value={searForm} onChange={setSearForm} />
<div>{JSON.stringify(searForm)}</div>
<div className="mt-2">
<button className="bg-blue-200 px-2 rounded cursor-pointer" onClick={onSubmit}>
提交
</button>
</div>
</div>
);
};
export default DemoTwo;
如果自己手写,也能实现
import { FC, useEffect, useState } from 'react';
interface SearForm {
name: string;
age: string;
}
interface HeadFormProps {
value: SearForm;
onChange: (values: SearForm) => void;
}
const HeadForm: FC<HeadFormProps> = (props) => {
const [state, _setState] = useState<SearForm>({ name: '11', age: '22'});
const [motto, setMotto] = useState('');
// 设置子组件状态,要同步到父组件
const setState = (value: SearForm) => {
_setState(value);
if (props.onChange) {
props.onChange(value);
}
};
// 监听父组件传入值的变更,同步至子组件
useEffect(() => {
if (props.value) {
_setState(props.value);
}
}, [props.value]);
return (
<div className="head-form">
<div className="form-groups">
<div className="form-group flex gap-1">
<label htmlFor="name">姓名</label>
<input className="border" type="text" value={state.name} onChange={(e) => setState({ ...state, name: e.target.value })} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">年龄</label>
<input className="border" type="text" value={state.age} onChange={(e) => setState({ ...state, age: e.target.value })} />
</div>
<div className="form-group flex gap-1 mt-2">
<label htmlFor="name">签名</label>
<input className="border" type="text" value={motto} onChange={(e) => setMotto(e.target.value)} />
</div>
</div>
</div>
);
};
const DemoTwo = () => {
const [searForm, setSearForm] = useState<SearForm>({ name: '33', age: '44' });
const onSubmit = () => {
console.log(searForm);
};
return (
<div className="wrapper">
<HeadForm value={searForm} onChange={setSearForm} />
<div>{JSON.stringify(searForm)}</div>
<div className="mt-2">
<button className="bg-blue-200 px-2 rounded cursor-pointer" onClick={onSubmit}>
提交
</button>
</div>
</div>
);
};
export default DemoTwo;


浙公网安备 33010602011771号