热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Reactprops完整解析

props是React组件通信最重要的手段,它在React的世界中充当的角色是十分重要的。学好props可以使组件间通信更加灵活,同时文中会介绍一些pr

props 是 React 组件通信最重要的手段,它在 React 的世界中充当的角色是十分重要的。学好 props 可以使组件间通信更加灵活,同时文中会介绍一些 props 的操作技巧,和学会如何编写嵌套组件。


一、Props 是什么

先来看一个 demo :

function Chidren(){return

我是子组件

}
/* props 接受处理 */
function Father(props) {const { children , mes , renderName , say ,Component } = propsconst renderFunction = children[0]const renderCompOnent= children[1]/* 对于子组件,不同的props是怎么被处理 */return (
{ renderFunction() }{ mes }{ renderName() }{ renderComponent }
)
}
/* props 定义绑定 */
class App extends React.Component{state={ mes: "hello,React"}node = nullsay= () => this.setState({ mes:'let us learn React!' })render(){return
my name is YinJie
} // ④ props 作为渲染函数>{ ()=>
hello,world
} { /* ⑤render props */ } { /* ⑥render component */ }
}
}

我们看一下输出结果:

当点击触发更改时就能够调用回调更改数据源:

所以 props 可以是:


  • ① props 作为一个子组件渲染数据源。
  • ② props 作为一个通知父组件的回调函数。
  • ③ props 作为一个单纯的组件传递。
  • ④ props 作为渲染函数。
  • ⑤ render props , 和④的区别是放在了 children 属性上。
  • ⑥ render component 插槽组件。


二、props children模式

我们先来看看 prop + children 的几个基本情况:

① props 插槽组件


上述可以在 Container 组件中,通过 props.children 属性访问到 Children 组件,为 React element 对象。

作用:


  • 可以根据需要控制 Children 是否渲染。

  • 像上一节所说的, Container 可以用 React.cloneElement 强化 props (混入新的 props ),或者修改 Children 的子元素。

举一个用 React.cloneElement 强化 props 的例子,多用于编写组件时对子组件混入新的 props,下面我们要做一个导航组件,我们希望它的结构如下:

activedisabledxyz

我们想给每个 MenuItem 子组件都添加 index 属性,这个事情不应该让用户手动添加,最好是可以在 Menu 组件中自动为每个 MenuItem 子组件添加上,并且 Menu 组件还应该判断子组件的类型,如果子组件的类型不是 MenuItem 组件就报错。


Menu.tsx:

const Menu: React.FC = (props) => {// ... 一些操作const renderChildren = () => { // 让子级的children都是 menuItem,有不是的就报错return React.Children.map(children, (child, index) => {const childElement = child as React.FunctionComponentElementconst { displayName } = childElement.typeif(displayName === 'MenuItem' || displayName === "SubMenu") {return React.cloneElement(childElement, { index: index.toString() })} else {console.error('warning: Menu has a child whitch is not a MenuItem')}})}return (

    {renderChildren()}
)
}

在 Menu 组件中我们通过 React.children.map 来循环子组件,通过 child.type 可以获取到每个子组件的 displayName 静态属性,这个在子组件中有定义:

通过子组件的 displayName 来判断是否是我们需要的 MenuItem,如果是的话就调用 React.cloneElement 来为子组件添加 index 属性。

② render props模式

{ (ContainerProps)=> }

这种情况,在 Container 中, props.children 属性访问到是函数,并不是 React element 对象,我们应该调用这个函数:

function Container(props) {const COntainerProps= {name: 'alien',mes:'let us learn react'}return props.children(ContainerProps)
}

这种方式作用是:


  • 1 根据需要控制 Children 渲染与否。
  • 2 可以将需要传给 Children 的 props 直接通过函数参数的方式传递给执行函数 children 。

3. render props模式

如果 Container 的 Children 既有函数也有组件,这种情况应该怎么处理呢?

{ (ContainerProps)=> }

const Children = (props)=> (

hello, my name is { props.name }
{ props.mes }

)function Container(props) {const COntainerProps= {name: 'alien',mes:'let us learn react'}return props.children.map(item=>{if(React.isValidElement(item)){ // 判断是 react elment 混入 propsreturn React.cloneElement(item,{ ...ContainerProps },item.props.children)}else if(typeof item === 'function'){return item(ContainerProps)}else return null})
}const Index = ()=>{return { (ContainerProps)=> }
}

这种情况需要先遍历 children ,判断 children 元素类型:


  • 针对 element 节点,通过 cloneElement 混入 props ;
  • 针对函数,直接传递参数,执行函数。


三、进阶实践-实现一个简单的  嵌套组件 

接下来到实践环节了。需要编写一个实践 demo ,用于表单状态管理的 和  组件


  • 用于管理表单状态;
  • 用于管理输入框组件。,

编写的组件能够实现的功能是:


  • Form 组件可以被 ref 获取实例。然后可以调用实例方法 submitForm 获取表单内容,用于提交表单,resetForm 方法用于重置表单。
  • Form组件自动过滤掉除了FormItem之外的其他React元素
  • FormItem 中 name 属性作为表单提交时候的 key ,还有展示的 label 。
  • ④ FormItem 可以自动收集  表单的值。

App.js:

import React, { useState, useRef } from "react";
import Form from './Form'
import FormItem from './FormItem'
import Input from './Input'function App () {const form = useRef(null)const submit =()=>{/* 表单提交 */form.current.submitForm((formValue)=>{ // 调用 form 中的submitForm方法console.log(formValue)})}const reset = ()=>{/* 表单重置 */form.current.resetForm() //调用 form 中的 resetForm 方法}return


}export default App

Form.js:

class Form extends React.Component{state={formData:{}}/* 用于提交表单数据 */submitForm=(cb)=>{cb({ ...this.state.formData })} /* 获取重置表单数据 */resetForm=()=>{const { formData } = this.stateObject.keys(formData).forEach(item=>{formData[item] = ''})this.setState({formData})}/* 设置表单数据层 */setValue=(name,value)=>{this.setState({formData:{...this.state.formData,[name]:value}})}render(){const { children } = this.propsconst renderChildren = []React.Children.forEach(children,(child)=>{if(child.type.displayName === 'formItem'){const { name } = child.props/* 克隆`FormItem`节点,混入改变表单单元项的方法 */const Children = React.cloneElement(child,{ key:name , /* 加入key 提升渲染效果 */handleChange:this.setValue , /* 用于改变 value */value:this.state.formData[name] || '' /* value 值 */},child.props.children)renderChildren.push(Children)}})return renderChildren}
}
/* 增加组件类型type */
Form.displayName = 'form'

设计思想:


  • 首先考虑到  在不使用 forwardRef 前提下,最好是类组件,因为只有类组件才能获取实例。
  • 创建一个 state 下的 formData属性,用于收集表单状态。
  • 要封装 重置表单提交表单改变表单单元项的方法。
  • 要过滤掉除了 FormItem 元素之外的其他元素,那么怎么样知道它是不是FormItem,这里教大家一种方法,可以给函数组件或者类组件绑定静态属性来证明它的身份,然后在遍历 props.children 的时候就可以在 React element 的 type 属性(类或函数组件本身)上,验证这个身份,在这个 demo 项目,给函数绑定的 displayName 属性,证明组件身份。
  • 要克隆 FormItem 节点,将改变表单单元项的方法 handleChange 和表单的值 value 混入 props 中。

FormItem.js:

function FormItem(props){const { children , name , handleChange , value , label } = propsconst OnChange= (value) => {/* 通知上一次value 已经改变 */handleChange(name,value)}return

{ label }:{React.isValidElement(children) && children.type.displayName === 'input' ? React.cloneElement(children,{ onChange , value }): null}

}
FormItem.displayName = 'formItem'

设计思想:


  • FormItem一定要绑定 displayName 属性,用于让  识别
  • 声明 onChange 方法,通过 props 提供给,作为改变 value 的回调函数。
  • FormItem过滤掉除了 input 以外的其他元素。

Input.js:

/* Input 组件, 负责回传value值 */
function Input({ onChange , value }){return ( onChange && onChange(e.target.value) ) } value={value} />
}
/* 给Component 增加标签 */
Input.displayName = 'input'

设计思想:


  • 绑定 displayName 标识input
  • input DOM 元素,绑定 onChange 方法,用于传递 value 。

下面通过函数组件再重写一下:

App.js,FormItem.js 和 Input.js 还是一样的,Form.js使用了 hooks 钩子来管理状态,并且通过 forwardRef, useImperativeHandle,让 App 组件访问到 Form 中的方法:

import React, { useState, forwardRef, useImperativeHandle } from "react"
const Form = (props, ref) =>{const { children } = propsconst [ formData, setFormData ] = useState({})useImperativeHandle(ref, () => ({submitForm: submitForm,resetForm: resetForm}))/* 用于提交表单数据 */const submitForm=(cb)=>{cb(formData)} /* 获取重置表单数据 */const resetForm=()=>{const newData = formDataObject.keys(newData).forEach(item=>{newData[item] = ''})setFormData(newData)}/* 设置表单数据层 */const setValue=(name,value)=>{setFormData({...formData,[name]:value})}const renderChildren = () => {return React.Children.map(children,(child)=>{if(child.type.displayName === 'formItem'){const { name } = child.props/* 克隆`FormItem`节点,混入改变表单单元项的方法 */const Children = React.cloneElement(child,{ key:name , /* 加入key 提升渲染效果 */handleChange: setValue , /* 用于改变 value */value: formData[name] || '' /* value 值 */},child.props.children)return Children}})}return ( renderChildren())
}
/* 增加组件类型type */
Form.displayName = 'form'export default forwardRef(Form)

启动项目,查看效果:

点击提交,我们在输入框里输入的内容就能显示在控制台上。

为了体现出咱们这个嵌套组件的高可复用性,我们可以在根组件中随意添加子项:

 


推荐阅读
author-avatar
琳琳小朋友m
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有