react中是以组件式进行开发,不可避免会有组件层级相互传值的情况。目前将组件通信的方法进行汇总,主要有以下几种。
一、props属 性形式进行通信
使用props形式 的通信,主要为父子组件的层级较少的情况。以弹出框dialog的 需求为例,弹出框中的外部样式是公共的,弹出框的内容需要跟进不同的业务逻辑进行显示。
// Dialog作为容器不关心内容和逻辑
// 等同于vue中slot
function Dialog(props) {
return (
<div style={{ border: `4px solid ${props.color || "blue"}` }}>
{props.children}
<div className="footer">{props.footer}</div>
</div>
);
}
function WelcomeDialog(props) {
return (
<Dialog {...props}>
<h1>欢迎光临</h1>
<p>感谢使用react</p>
</Dialog>
);
}
export default function(){
const footer=<button onClick='()=>alert(1)'>确定</button>
return <WelcomeDialog color="g reen" footer={footer} />
}
上面弹出框的封装使用props传参,并且使用了props中children属性。
- props中的children可以是组件(上面例子),也可以是数组、函数、对象等。为了方便操作children为数组或对象的情况.React提供了一系列函数助手,使得操作children更加方便。
- 两个最显眼的函数助手就是 React.Children.map 以及 React.Children.forEach。它们在对应数组的情况下能起作用,除此之外,当函数、对象或者任何东西作为children传递时,它们也会起作用。
例子:封装一个radio框,在将radio的name属性和传入的value属性一致。
// 修改children
function RadioGroup(props) {
return (
<div>
{React.Children.map(props.children, child => {
// vdom不可更改,克隆一个新的去改才行
return React.cloneElement(child, { name: props.name });
})}
</div>
);
}
// rest 除了children属性之后的其他属性,放到radio属性中
function Radio({children, ...rest}) {
return (
<label>
<input type="radio" {...rest} />
{children}
</label>
);
}
使用方法
<RadioGroup name="mvvm">
<Radio value="vue">vue</Radio>
<Radio value="react">react</Radio>
<Radio value="react">angular</Radio>
</RadioGroup>
props:不同层级组件形式的传递形式,父-子传递,子-父传递,兄弟传递
二、使用上下文content进行通信
context提供了无需为每层组件手动添加props,就能在组件树中进行数据传递的办法
context设计的目的是为了共享哪些对于组件树而言全局的数据。使用context可以避免使用props进行多层传递的问题。
使用context要谨慎,因为使用context,组件的复用性会比较差,如果要避免层层传递的一些属性,组件组合是一种比较好的方式
import React, { useContext } from "react";
// 1.创建上下文
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
function Child(prop) {
return <div>Child: {prop.foo}</div>;
}
// 使用hook消费
function Child2() {
const context = useContext(MyContext);
return <div>Child2: {context.foo}</div>;
}
// 使用class指定静态contextType
class Child3 extends React.Component {
// 设置静态属性通知编译器获取上下文中数据并赋值给this.context
static contextType = MyContext;
render() {
return <div>Child3: {this.context.foo}</div>
}
}
export default function ContextTest() {
return (
<div>
<Provider value={{ foo: "bar" }}>
{/* 消费方法1:Consumer */}
<Consumer>{value => <Child {...value} />}</Consumer>
{/* 消费方法2:hook */}
<Child2 />
{/* 消费方法3:contextType */}
<Child3 />
</Provider>
</div>
);
}
1、创建Context
使用createContext可以创建上下文(context),这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
const MyContext = React.createContext(defaultValue);
{Provider,Consumer}=MyContext
从创建的context对象中,可以解构出Provider(提供者组件-注入值)和Consumer(消费组件-获取值)
2、创建context后,在顶层使用Provider组件注入全局属性。
<Provider value={/* 某个值 */}>
获取在子组件中获取属性的3种形式。
- 使用consumer组件
<Consumer>{value => <Child {...value} />}</Consumer>
- 使用hook(useContext)消费
// 使用hook消费
function Child2() {
const context = useContext(MyContext);
return <div>Child2: {context.foo}</div>;
}
- 使用class指定静态contextType
class Child3 extends React.Component {
// 设置静态属性通知编译器获取上下文中数据并赋值给this.context
static contextType = MyContext;
render() {
return <div>Child3: {this.context.foo}</div>
}
}
三、高阶组件
具体而言,高阶组件是参数为组件,返回值为新组件的函数。高阶组件 不是React的API,而是 一种组件 复用的设计模式。