当前位置: 首页 > 工具软件 > Form'n'Field > 使用案例 >

React context使用及Form表单的封装

万坚壁
2023-12-01

描述

组件通信在模块化开发中是必不可少的功能,无非就是子父,父子,兄弟,隔代组件通信。
本文主要讲解react 中使用context跨层级通信的使用。最后会有form表单组件的封装帮助你更深的理解context

react 组件通信几种方式:
props 子父 父子
context 跨层级
redux订阅模式

context使用

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。

Form表单封装

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中。

 类似资料: