组件通信在模块化开发中是必不可少的功能,无非就是子父,父子,兄弟,隔代组件
通信。
本文主要讲解react 中使用context跨层级通信
的使用。最后会有form表单
组件的封装帮助你更深的理解context
在
react
组件通信几种方式:
props
子父 父子
context
跨层级
redux
和订阅模式
1 createContext
创建一个context对象
// context.js
import React from "react"
// 创建context对象 | 可以写入默认值
export const Context = React.createContext()
2 provide
创建provider 传递value
// ContextPage.js
import Context from "./Context"
import ContextTypePage from "./ContextTypePage"
import UseContextPage from "./UseContextPage"
import ConsumerPage from "./ConsumerPage"
export default class ContextPage extends Component {
constructor(props) {
super(props)
this.state = {
user: { name: "sjs" }
}
}
redner() {
const { user } = this.state
return (
<div>
// 创建previde 传递value
// 此处的value不用直接赋值对象,要赋值到state中
<Context.Provider value={ user }>
</Context.Provider>
</div>
)
}
}
3 子组件消费value 有三种方式contextType
useContext
consumer
// ContextTypePage
import Context from "./Context"
export default class ContextTypePage extends Component {
// 此处需注意,只能订阅单一的context来源,多个会进行覆盖
static contextType = Context
render() {
const { user } = this.context
return (
<div>
<h3>{ user.name }</h3>
</div>
)
}
}
// UseContextPage
import React from 'react'
import Context from "./Context"
export default function UseContextPage() {
// hookApi可以订阅多个context
const user = React.useContext(Context)
return (
<div>{ user.name }</div>
)
}
// ConsumerPage
import Context from "./Context"
export default class ConsumerPage extends Component {
render() {
return (
<div>
<Context.Consumer>
{
(user) => {
<p>{ user.name }</p>
}
}
</Context.Consumer>
// 上面写法不是太美观,可以把函数提出去
<Context.Consumer>
{ user => <User {...user}/> }
</Context.Consumer>
</div>
)
}
}
function User(props) {
return <p>{props.name}</p>
}
看完上述代码可能会有疑问,三种方式怎么去
选择
?
contextType: 只能用在类组件
,只能订阅单一
的context来源
useContext: 不难看出是一个hook api,那就很明确,只能用在函数组件
consumer:不限制
函数和类组件, 但是需要注意,这种方法需要一个函数
作为子元素函数接收当前的context
值,并返回一个 React 节点。
context使用已经写完了,希望看到这里的你有所收获。下面封装一个from表单,巩固下context。
target:
antd 4的form表单源码实现
实现表单提交前的validate
// FieldContext
import React from 'react'
const FieldContext = React.createContext()
export default FieldContext
// Field
import React, { Component } from 'react'
import FieldContext from './FieldContext'
export default class Field extends Component {
static contextType = FieldContext
componentDidMount() {
this.unRegisterField = this.context.registerField(this)
}
componentWillUnmount() {
this.unRegisterField()
}
onStoreChange = () => {
this.forceUpdate()
}
getControl = () => {
const { getFieldValue, setFiledsValue } = this.context
const { name } = this.props
return {
value: getFieldValue(name),
onChange: (e) => {
const newValue = e.target.value
setFiledsValue({ [name]: newValue })
}
}
}
render() {
const { children } = this.props
return React.cloneElement(children, this.getControl())
}
}
// Form
import React from 'react'
import FieldContext from './FieldContext'
import useForm from './useForm'
export default function Form({ children, form, onFinish, onFinishFailed }) {
const [formInstance] = useForm(form)
formInstance.resultControll({onFinish, onFinishFailed})
// useFrom 出来的数据仓库的控制权
return <form onSubmit={(e) => {
e.preventDefault()
formInstance.submit()
}}>
<FieldContext.Provider value={formInstance}>
{children}
</FieldContext.Provider>
</form>
}
// useForm
import { useRef } from 'react'
export default function useForm(form) {
// useRef 就是虚拟dom节点 fiber
const formRef = useRef()
if (!formRef.current) {
if (form) {
formRef.current = form
} else {
const formStore = new FormStore()
formRef.current = formStore.getForm()
}
}
return [formRef.current];
}
class FormStore {
constructor() {
this.store = {}
this.fields = []
this.cbs = {}
this.validataTipMsg = {}
}
resultControll = (newCbs) => {
console.log(newCbs)
this.cbs = {
...this.cbs,
...newCbs
}
}
// 订阅 注册
registerField = (field) => {
this.fields.push(field)
// 取消订阅 取消注册
return () => {
this.fields = this.fields.filter(n => n !== field)
delete this.store[field.props.name]
}
}
setFieldsValue = (newStore) => {
this.store = {
...this.store,
...newStore,
};
// 更新组件
this.fields.forEach(n => {
Object.keys(newStore).forEach(key => {
n.props.name === key && n.onStoreChange()
})
})
}
getFieldsValue = () => {
return { ...this.store }
}
getFieldValue = (key) => {
return this.store[key]
}
validate = () => {
let arr = []
return arr
}
submit = () => {
const err = this.validate()
console.log('err', err)
const { onFinish, onFinishFailed } = this.cbs
if (err.length > 0) {
onFinishFailed(err, this.getFieldsValue())
} else {
onFinish(this.getFieldsValue())
}
}
getForm = () => {
return {
getFieldValue: this.getFieldValue,
getFieldsValue: this.getFieldsValue,
setFiledsValue: this.setFieldsValue,
registerField: this.registerField,
validate: this.validate,
submit: this.submit,
resultControll: this.resultControll,
}
}
}
// MyFieldForm
import Form from "./Form"
const nameRules = { required: true, message: "请输入名称!" };
const passworRules = { required: true, message: "请输入密码!" };
export default function MyFieldForm() {
const [form] = Form.useForm();
const onFinish = (val) => {
console.log("onFinish", val);
};
const onFinishFailed = (err, val) => {
console.log("onFinishFailed",err, val);
};
return (
<div>
<h3>MyRCFieldForm</h3>
<Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed}>
<Field name="username" rules={[nameRules]}>
<Input placeholder="input UR Username" />
</Field>
<Field name="password" rules={[passworRules]}>
<Input placeholder="input UR Password" />
</Field>
<button>Submit</button>
</Form>
</div>
);
}
Form表单写的比较零碎,完整代码 https://gitee.com/uijwuy/react-ex,有兴趣自取。
Tips: Provider 传递value
的时候需要注意不能直接赋值对象
,因为React组件更新机制是props
是否发生变化,如果直接把对象赋值上(对象的引用地址一直发生变化),就会出现不必要的更新。解决把对象提取到state中。