使用Context进行跨层级通信
使用组件化的网页开发方法, 组件之间的通信是个避免不了的话题。组件之间的通信方式也是多种多样,现在我们就来介绍一种使用Context进行跨层级的通信的方式。
首先, 一般我们会先在一个单独的文件中, 比如context.js。定义如下的变量:
import React from 'react';
export const ThemeContext = React.createContext();
export const ThemeProvider = ThemeContext.Provider;
export const ThemeConsumer = ThemeContext.Consumer;
然后在祖先组件中,使用Provider对象向子孙组件传值。
<ThemeProvider value={this.state.theme}>
<HomePage />
<UsersPage />
</ThemeProvider>
注意如果用以下字面量的方式传递Provider的值,React每次都会重新渲染它的子组件,因为在JavaScript中两个字面值对象总是不相等的。
{themeColor: 'red'} !== {themeColor: 'red'}
在Class子组件中, 可以这样拿到Provider提供的值。 可以使用Consumer对象拿到Provider提供的值:
<ThemeConsumer>
{theme=><h1 style={{color: theme.themeColor}}>大王叫我去巡山</h1>}
</ThemeConsumer>
首先在Class中定义静态属性contextType。
static contextType = ThemeContext;
然后可以如下列代码拿到Provider的值:
render() {
const { themeColor } = this.context;
return <div>
<h1 style={{color: themeColor}}>大王叫我去巡山</h1>
</div>
}
在函数组件中,可以使用useContext方法拿到祖先组件传下来的值。如下代码:
export function UsersPage(props) {
const {themeColor} = useContext(ThemeContext);
return (
<div style={{color:themeColor}}> 我去你家啊!</div>
);
};
高阶组件
React官方定义,高阶组件为一个函数,接收一个组件,返回另一个组件。
const foo = Cmp => props => {
return (
<div className="border">
<Cmp {...props} />
</div>
);
};
开发中,通常使用高级组件对原有组件进行属性代理(添加新的属性),或者对原有组件进行包装,返回一个新的组件类型。
我们可以像调用普通函数那样使用高阶组件,也可以是使用ES7中的装饰器语法。
@foo
class Child extends Component {
render() {
return (
<div>HOC {this.props.name}</div>
);
}
}
递归组件
递归组件通常用来渲染递归数据,如下列代码中的 nodes。
this.nodes = [
{
val: "v",
children: [
{ val: "v2" },
{ val: "v3" },
{ val: "v4", children: [{ val: "vv2" }, { val: "vv3" }] },
],
},
{
val: "t",
children: [
{ val: "t2" },
{ val: "t3" },
{ val: "t4", children: [{ val: "tt2" }, { val: "tt3" }] },
],
},
];
要实现递归组件,其实只要像调用递归函数那样调用自身即可, 如下代码所示!
class Node extends Component {
render() {
let { val, children } = this.props.node;
return (
<div>
<h1>{val}</h1>
<div>{children && children.map((node) => <Node node={node} />)}</div>
</div>
);
}
}
仿 Antd4 表单组件
熟悉 Antd4 的同学,都应该对他的 Form 表单的使用比较熟悉了。如下列代码所示:
<Form
form={form}
onFinish={(values) => {
console.log("onFinish......", values);
}}
onFinishFailed={() => {
console.log("onFinishFailed......");
}}
>
<Field name="username">
<input placeholder="太好了" />
</Field>
<Field name="password">
<input placeholder="太好了" />
</Field>
<button type="submit">提交</button>
</Form>
要实现上述功能,通常需要使用Context对象,在Form组件中向下传递一个FormStore类的实例。
Context类型定义如下:
import { createContext } from 'react';
const FieldContext = createContext()
const FieldProvider = FieldContext.Provider
const FieldConsumer = FieldContext.Consumer
Form组件的定义如下:
import { FieldProvider } from "./FieldContext";
export default function Form({ children, form, onFinish, onFinishFailed }) {
form.setCallback({
onFinish,
onFinishFailed,
})
return (
<form
onSubmit={(event) => {
event.preventDefault();
form.submit();
}}
>
<FieldProvider value={form}>{children}</FieldProvider>
</form>
);
}
FormStore类型定义如下:
import React from 'react';
class FormStore{
constructor(){
this.store = {}
this.fieldEntities = []
this.callbacks = {}
}
registerField = entity => {
this.fieldEntities.push(entity)
return ()=>{
this.fieldEntities = this.fieldEntities.filter(item => item !== entity)
delete this.store[entity.props.name]
}
}
getFieldValue = name => {
return this.store[name];
}
setFieldsValue = newStore => {
this.store = {
...this.store,
...newStore,
}
this.fieldEntities.forEach(entity => {
let {name} = entity.props;
Object.keys(newStore).forEach(key => {
if(name === key){
entity.onStoreChange();
}
})
})
}
submit = () => {
this.callbacks['onFinish'](this.store);
}
setCallback = (callbacks) => {
this.callbacks = {
... callbacks,
... this.callbacks
}
}
getForm = () => {
return {
submit: this.submit,
registerField: this.registerField,
getFieldValue: this.getFieldValue,
setFieldsValue: this.setFieldsValue
}
}
}
export default function useForm(){
const formRef = React.useRef();
if(!formRef.current){
const formstore = new FormStore();
formRef.current = formstore;
}
return [formRef.current];
}
Field定义如下:
import React, { Component } from "react";
import { FieldContext } from "./FieldContext";
export default class Field extends Component {
static contextType = FieldContext;
componentDidMount() {
const { registerField } = this.context;
this.unregisterField = registerField(this);
}
componentWillUnmount() {
if (this.unregisterField) {
this.unregisterField();
}
}
onStoreChange = () => {
this.forceUpdate();
};
getControlled = () => {
const { name } = this.props;
const { getFieldValue, setFieldsValue } = this.context;
return {
value: getFieldValue(name) || "",
onChange: (event) => {
const newValue = event.target.value;
// console.log("newValue", newValue);
setFieldsValue({ [name]: newValue });
},
};
};
render() {
const { children } = this.props;
const returnChildNode = React.cloneElement(children, this.getControlled());
return returnChildNode;
}
}