如果你学过 Vue,那一定会知道每个应用都是由众多小组件构成的。拿一台手机来打比方,手机的硬件系统由主板、内存、按键等模块构成,这些不同的模块就属于手机的“组件”。
到了前端领域,组件一般都是各种页面上的按钮、开关、导航栏等元素。一般来说,这些组件之间需要相互传递数据,并产生对应的变化,那么我们在写 React 的时候是怎么实现组件之间通信的呢?(文中代码均采用函数组件+TypeScript 写法,供参考)
组件之间通信的类型
- 父组件——子组件
- 子组件——父组件
- 跨级组件
- 兄弟组件
父传子:props和自定义函数事件
父组件可以直接向子组件传递 props
,子组件接收到数据后进行更新,再同步给父组件。
function Money() {
const [selected, setSelected] = useState('food');
return (
<TagsSection value={selected}/>
);
}
复制代码
type Props = {
//声明props的数据类型
value: string;
}
const TagsSection: React.FC<Props> = (props) => {
const tagName = props.value;
return (
<div>the tag name is { tagName }</div>
);
};
复制代码
子传父:props.onchange
props.onchange
相当于子组件发给父组件的一个提醒:我这里的数据变了,你那边也要更新!
const TagsSection: React.FC<Props> = (props) => {
const onToggleTag = (tagId: number) => {
const index = selectedTagIds.indexOf(tagId);
if (index >= 0) {
props.onChange(selectedTagIds.filter(t => t !== tagId));
} else {
props.onChange([...selectedTagIds, tagId]);
}
};
}
复制代码
子组件 TagsSection
内部有个 onToggleTag
函数,判断传入的 tagId
索引是否大于或等于0。如果大于等于0,那么使用 selectedTagIds.filter
方法,然后用 props.onchange
把数据传给父组件,让父组件来改变数据。
注意:如果有需要同步给父组件的数据,请务必使用 props.onchange
,否则子组件自己改了数据,父组件都不知道,拿就无法同步了。
跨级组件:Context
一般跨级组件都是一些多组件公用的数据,比如我们登录的用户信息、UI主题等等。如果组件的层级比较深,如果只是用 props ,只能通过一层一层的组件来接力下去,还挺麻烦的。
function App(){
return (
<Toolbar theme="dark">
)
}
function Toolbar(props) {
return (
<div>
<ThemeButton theme={props.theme} />
</div>
);
}
function ThemeButton(){
return (
<Button theme={props.theme} />
)
}
复制代码
在上面的代码中,子组件 Button 想要拿到 themecolor
,那么就得把 theme
作为 props
,从 App
一路传到 ThemeButton
。
针对这样的情况,React 提供了Context
,可以实现多层级的组件穿透。
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemeButton />
</div>
);
}
function ThemeButton() {
render() {
return (
<ThemeContext.Consumer>
{value=><Button theme={value} />}
</ThemeContext.Consumer>
);
}
}
复制代码
React.createContext
创建了一个 context
对象,如果某个组件订阅了这个对象,在渲染的时候,会从离这个组件最近的一个 Provider
组件中获取当前 context
。
Context
的使用方式:
- 使用
const xxx = createContext(null)
创建上下文 - 使用
<xxx.Provider>
圈定作用域 - 在作用域内使用
useContext(xxx)
来使用上下文
同级组件共享数据源
如果父组件中,含有两个及以上的组件之间要进行通信,那么我们可以把数据源存放在它们共同的父组件中。
function Money() {
const [selected, setSelected] = useState(defaultFormData);
const onChange = (obj: Partial<Selected>) => {
setSelected({...selected, ...obj});
};
return (
<CategorySection value={selected.category}
onChange={(category) => onChange({category})}/>
<TagsSection value={selected.tagIds}
onChange={(tagIds) => onChange({tagIds})}/>
);
}
复制代码
案例中有 CategorySection
和 TagsSection
两个子组件,他们的 value
都来自于父组件 Money
的 selected
。