React?props全面詳細解析
一、Props 是什么
先來看一個 demo :
function Chidren(){
return <div> 我是子組件 </div>
}
/* props 接受處理 */
function Father(props) {
const { children , mes , renderName , say ,Component } = props
const renderFunction = children[0]
const renderComponent = children[1]
/* 對于子組件,不同的props是怎么被處理 */
return (
<div>
{ renderFunction() }
{ mes }
{ renderName() }
{ renderComponent }
<Component />
<button onClick={ () => say() } > 觸發(fā)更改 </button>
</div> )
}
/* props 定義綁定 */
class App extends React.Component{
state={
mes: "hello,React"
}
node = null
say= () => this.setState({ mes:'let us learn React!' })
render(){
return <div>
<Father
mes={this.state.mes} // ① props 作為一個渲染數(shù)據(jù)源
say={ this.say } // ② props 作為一個回調函數(shù) callback
Component={ Chidren } // ③ props 作為一個組件
renderName={ ()=><div> my name is YinJie </div> } // ④ props 作為渲染函數(shù)
>
{ ()=> <div>hello,world</div> } { /* ⑤render props */ }
<Chidren /> { /* ⑥r(nóng)ender component */ }
</Father>
</div>
}
}我們看一下輸出結果:

當點擊觸發(fā)更改時就能夠調用回調更改數(shù)據(jù)源:

所以 props 可以是:
① props 作為一個子組件渲染數(shù)據(jù)源。
② props 作為一個通知父組件的回調函數(shù)。
③ props 作為一個單純的組件傳遞。
④ props 作為渲染函數(shù)。
⑤ render props , 和④的區(qū)別是放在了 children 屬性上。
⑥ render component 插槽組件。
二、props children模式
我們先來看看 prop + children 的幾個基本情況:
1. props 插槽組件
<Container>
<Children>
</Container>上述可以在 Container 組件中,通過 props.children 屬性訪問到 Children 組件,為 React element 對象。
作用:
- 可以根據(jù)需要控制 Children 是否渲染。
- 像上一節(jié)所說的, Container 可以用 React.cloneElement 強化 props (混入新的 props ),或者修改 Children 的子元素。
舉一個用React.cloneElement 強化 props 的例子,多用于編寫組件時對子組件混入新的 props,下面我們要做一個導航組件,我們希望它的結構如下:
<Menu>
<MenuItem >
active
</MenuItem>
<MenuItem>
disabled
</MenuItem>
<MenuItem >
xyz
</MenuItem>
</Menu>我們想給每個 MenuItem 子組件都添加 index 屬性,這個事情不應該讓用戶手動添加,最好是可以在 Menu 組件中自動為每個 MenuItem 子組件添加上,并且 Menu 組件還應該判斷子組件的類型,如果子組件的類型不是 MenuItem 組件就報錯。
Menu.tsx:
const Menu: React.FC<MenuProps> = (props) => {
// ... 一些操作
const renderChildren = () => { // 讓子級的children都是 menuItem,有不是的就報錯
return React.Children.map(children, (child, index) => {
const childElement = child as React.FunctionComponentElement<MenuItemProps>
const { displayName } = childElement.type
if(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 (
<ul className={classes} style={style} data-testid="test-menu">
<MenuContext.Provider value={passedContext}>
{renderChildren()}
</MenuContext.Provider>
</ul>
)
}在 Menu 組件中我們通過 React.children.map 來循環(huán)子組件,通過 child.type 可以獲取到每個子組件的 displayName 靜態(tài)屬性,這個在子組件中有定義:

通過子組件的 displayName 來判斷是否是我們需要的 MenuItem,如果是的話就調用 React.cloneElement 來為子組件添加 index 屬性。
2. render props模式
<Container>
{ (ContainerProps)=> <Children {...ContainerProps} /> }
</Container>這種情況,在 Container 中, props.children 屬性訪問到是函數(shù),并不是 React element 對象,我們應該調用這個函數(shù):
function Container(props) {
const ContainerProps = {
name: 'alien',
mes:'let us learn react'
}
return props.children(ContainerProps)
}這種方式作用是:
1 根據(jù)需要控制 Children 渲染與否。
2 可以將需要傳給 Children 的 props 直接通過函數(shù)參數(shù)的方式傳遞給執(zhí)行函數(shù) children 。
3. render props模式
如果 Container 的 Children 既有函數(shù)也有組件,這種情況應該怎么處理呢?
<Container>
<Children />
{ (ContainerProps)=> <Children {...ContainerProps} name={'haha'} /> }
</Container>const Children = (props)=> (<div>
<div>hello, my name is { props.name } </div>
<div> { props.mes } </div>
</div>)
function Container(props) {
const ContainerProps = {
name: 'alien',
mes:'let us learn react'
}
return props.children.map(item=>{
if(React.isValidElement(item)){ // 判斷是 react elment 混入 props
return React.cloneElement(item,{ ...ContainerProps },item.props.children)
}else if(typeof item === 'function'){
return item(ContainerProps)
}else return null
})
}
const Index = ()=>{
return <Container>
<Children />
{ (ContainerProps)=> <Children {...ContainerProps} name={'haha'} /> }
</Container>
}這種情況需要先遍歷 children ,判斷 children 元素類型:
- 針對 element 節(jié)點,通過 cloneElement 混入 props ;
- 針對函數(shù),直接傳遞參數(shù),執(zhí)行函數(shù)。
三、進階實踐
實現(xiàn)一個簡單的<Form> <FormItem>嵌套組件
接下來到實踐環(huán)節(jié)了。需要編寫一個實踐 demo ,用于表單狀態(tài)管理的<Form>和<FormItem>組件
<Form>用于管理表單狀態(tài);<FormItem>用于管理<Input>輸入框組件。,
編寫的組件能夠實現(xiàn)的功能是:
①Form組件可以被 ref 獲取實例。然后可以調用實例方法submitForm獲取表單內(nèi)容,用于提交表單,resetForm方法用于重置表單。
②Form組件自動過濾掉除了FormItem之外的其他React元素
③FormItem中 name 屬性作為表單提交時候的 key ,還有展示的 label 。
④FormItem可以自動收集<Input/>表單的值。
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 <div className='box' >
<Form ref={ form } >
<FormItem name="name" label="我是" >
<Input />
</FormItem>
<FormItem name="mes" label="我想對大家說" >
<Input />
</FormItem>
<FormItem name="lees" label="ttt" >
<Input />
</FormItem>
</Form>
<div className="btns" >
<button className="searchbtn" onClick={ submit } >提交</button>
<button className="concellbtn" onClick={ reset } >重置</button>
</div>
</div>
}
export default AppForm.js:
class Form extends React.Component{
state={
formData:{}
}
/* 用于提交表單數(shù)據(jù) */
submitForm=(cb)=>{
cb({ ...this.state.formData })
}
/* 獲取重置表單數(shù)據(jù) */
resetForm=()=>{
const { formData } = this.state
Object.keys(formData).forEach(item=>{
formData[item] = ''
})
this.setState({
formData
})
}
/* 設置表單數(shù)據(jù)層 */
setValue=(name,value)=>{
this.setState({
formData:{
...this.state.formData,
[name]:value
}
})
}
render(){
const { children } = this.props
const renderChildren = []
React.Children.forEach(children,(child)=>{
if(child.type.displayName === 'formItem'){
const { name } = child.props
/* 克隆`FormItem`節(jié)點,混入改變表單單元項的方法 */
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'設計思想:
- 首先考慮到
<Form>在不使用forwardRef前提下,最好是類組件,因為只有類組件才能獲取實例。 - 創(chuàng)建一個 state 下的 formData屬性,用于收集表單狀態(tài)。
- 要封裝重置表單,提交表單,改變表單單元項的方法。
- 要過濾掉除了
FormItem元素之外的其他元素,那么怎么樣知道它是不是FormItem,這里教大家一種方法,可以給函數(shù)組件或者類組件綁定靜態(tài)屬性來證明它的身份,然后在遍歷 props.children 的時候就可以在 React element 的 type 屬性(類或函數(shù)組件本身)上,驗證這個身份,在這個 demo 項目,給函數(shù)綁定的 displayName 屬性,證明組件身份。 - 要克隆
FormItem節(jié)點,將改變表單單元項的方法 handleChange 和表單的值 value 混入 props 中。
FormItem.js:
function FormItem(props){
const { children , name , handleChange , value , label } = props
const onChange = (value) => {
/* 通知上一次value 已經(jīng)改變 */
handleChange(name,value)
}
return <div className='form' >
<span className="label" >{ label }:</span>
{
React.isValidElement(children) && children.type.displayName === 'input'
? React.cloneElement(children,{ onChange , value })
: null
}
</div>
}
FormItem.displayName = 'formItem'設計思想:
FormItem一定要綁定 displayName 屬性,用于讓<Form>識別<FormItem />- 聲明
onChange方法,通過 props 提供給<Input>,作為改變 value 的回調函數(shù)。 FormItem過濾掉除了input以外的其他元素。
Input.js:
/* Input 組件, 負責回傳value值 */
function Input({ onChange , value }){
return <input className="input" onChange={ (e)=>( onChange && onChange(e.target.value) ) } value={value} />
}
/* 給Component 增加標簽 */
Input.displayName = 'input'設計思想:
- 綁定 displayName 標識
input。 inputDOM 元素,綁定 onChange 方法,用于傳遞 value 。
下面通過函數(shù)組件再重寫一下:
App.js,F(xiàn)ormItem.js 和 Input.js 還是一樣的,F(xiàn)orm.js使用了 hooks 鉤子來管理狀態(tài),并且通過forwardRef, useImperativeHandle,讓 App 組件訪問到 Form 中的方法:
import React, { useState, forwardRef, useImperativeHandle } from "react"
const Form = (props, ref) =>{
const { children } = props
const [ formData, setFormData ] = useState({})
useImperativeHandle(ref, () => ({
submitForm: submitForm,
resetForm: resetForm
}))
/* 用于提交表單數(shù)據(jù) */
const submitForm=(cb)=>{
cb(formData)
}
/* 獲取重置表單數(shù)據(jù) */
const resetForm=()=>{
const newData = formData
Object.keys(newData).forEach(item=>{
newData[item] = ''
})
setFormData(newData)
}
/* 設置表單數(shù)據(jù)層 */
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`節(jié)點,混入改變表單單元項的方法 */
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) 啟動項目,查看效果:

點擊提交,我們在輸入框里輸入的內(nèi)容就能顯示在控制臺上。
為了體現(xiàn)出咱們這個嵌套組件的高可復用性,我們可以在根組件中隨意添加子項:

到此這篇關于React props全面詳細解析的文章就介紹到這了,更多相關React props內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解react中useCallback內(nèi)部是如何實現(xiàn)的
前幾天有人在問在useCallback函數(shù)如果第二個參數(shù)為空數(shù)組, 為什么拿不到最新的state值,那么這一章就來分析一下useCallback內(nèi)部是如何實現(xiàn)的,感興趣的小伙伴跟著小編一起來學習吧2023-07-07
react函數(shù)組件useState異步,數(shù)據(jù)不能及時獲取到的問題
這篇文章主要介紹了react函數(shù)組件useState異步,數(shù)據(jù)不能及時獲取到的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08

