React?props全面詳細(xì)解析
一、Props 是什么
先來看一個(gè) 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 作為一個(gè)渲染數(shù)據(jù)源 say={ this.say } // ② props 作為一個(gè)回調(diào)函數(shù) callback Component={ Chidren } // ③ props 作為一個(gè)組件 renderName={ ()=><div> my name is YinJie </div> } // ④ props 作為渲染函數(shù) > { ()=> <div>hello,world</div> } { /* ⑤render props */ } <Chidren /> { /* ⑥r(nóng)ender component */ } </Father> </div> } }
我們看一下輸出結(jié)果:
當(dāng)點(diǎn)擊觸發(fā)更改時(shí)就能夠調(diào)用回調(diào)更改數(shù)據(jù)源:
所以 props 可以是:
① props 作為一個(gè)子組件渲染數(shù)據(jù)源。
② props 作為一個(gè)通知父組件的回調(diào)函數(shù)。
③ props 作為一個(gè)單純的組件傳遞。
④ props 作為渲染函數(shù)。
⑤ render props , 和④的區(qū)別是放在了 children 屬性上。
⑥ render component 插槽組件。
二、props children模式
我們先來看看 prop + children 的幾個(gè)基本情況:
1. props 插槽組件
<Container> <Children> </Container>
上述可以在 Container 組件中,通過 props.children 屬性訪問到 Children 組件,為 React element 對象。
作用:
- 可以根據(jù)需要控制 Children 是否渲染。
- 像上一節(jié)所說的, Container 可以用 React.cloneElement 強(qiáng)化 props (混入新的 props ),或者修改 Children 的子元素。
舉一個(gè)用React.cloneElement 強(qiáng)化 props 的例子,多用于編寫組件時(shí)對子組件混入新的 props,下面我們要做一個(gè)導(dǎo)航組件,我們希望它的結(jié)構(gòu)如下:
<Menu> <MenuItem > active </MenuItem> <MenuItem> disabled </MenuItem> <MenuItem > xyz </MenuItem> </Menu>
我們想給每個(gè) MenuItem 子組件都添加 index 屬性,這個(gè)事情不應(yīng)該讓用戶手動(dòng)添加,最好是可以在 Menu 組件中自動(dòng)為每個(gè) MenuItem 子組件添加上,并且 Menu 組件還應(yīng)該判斷子組件的類型,如果子組件的類型不是 MenuItem 組件就報(bào)錯(cuò)。
Menu.tsx:
const Menu: React.FC<MenuProps> = (props) => { // ... 一些操作 const renderChildren = () => { // 讓子級的children都是 menuItem,有不是的就報(bào)錯(cuò) 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 可以獲取到每個(gè)子組件的 displayName 靜態(tài)屬性,這個(gè)在子組件中有定義:
通過子組件的 displayName 來判斷是否是我們需要的 MenuItem,如果是的話就調(diào)用 React.cloneElement 來為子組件添加 index 屬性。
2. render props模式
<Container> { (ContainerProps)=> <Children {...ContainerProps} /> } </Container>
這種情況,在 Container 中, props.children 屬性訪問到是函數(shù),并不是 React element 對象,我們應(yīng)該調(diào)用這個(gè)函數(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ù)也有組件,這種情況應(yīng)該怎么處理呢?
<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é)點(diǎn),通過 cloneElement 混入 props ;
- 針對函數(shù),直接傳遞參數(shù),執(zhí)行函數(shù)。
三、進(jìn)階實(shí)踐
實(shí)現(xiàn)一個(gè)簡單的<Form> <FormItem>嵌套組件
接下來到實(shí)踐環(huán)節(jié)了。需要編寫一個(gè)實(shí)踐 demo ,用于表單狀態(tài)管理的<Form>
和<FormItem>
組件
<Form>
用于管理表單狀態(tài);<FormItem>
用于管理<Input>
輸入框組件。,
編寫的組件能夠?qū)崿F(xiàn)的功能是:
①Form
組件可以被 ref 獲取實(shí)例。然后可以調(diào)用實(shí)例方法submitForm
獲取表單內(nèi)容,用于提交表單,resetForm
方法用于重置表單。
②Form
組件自動(dòng)過濾掉除了FormItem
之外的其他React元素
③FormItem
中 name 屬性作為表單提交時(shí)候的 key ,還有展示的 label 。
④FormItem
可以自動(dòng)收集<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)=>{ // 調(diào)用 form 中的submitForm方法 console.log(formValue) }) } const reset = ()=>{ /* 表單重置 */ form.current.resetForm() //調(diào)用 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 App
Form.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è)置表單數(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é)點(diǎn),混入改變表單單元項(xiàng)的方法 */ 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'
設(shè)計(jì)思想:
- 首先考慮到
<Form>
在不使用forwardRef
前提下,最好是類組件,因?yàn)橹挥蓄惤M件才能獲取實(shí)例。 - 創(chuàng)建一個(gè) state 下的 formData屬性,用于收集表單狀態(tài)。
- 要封裝重置表單,提交表單,改變表單單元項(xiàng)的方法。
- 要過濾掉除了
FormItem
元素之外的其他元素,那么怎么樣知道它是不是FormItem
,這里教大家一種方法,可以給函數(shù)組件或者類組件綁定靜態(tài)屬性來證明它的身份,然后在遍歷 props.children 的時(shí)候就可以在 React element 的 type 屬性(類或函數(shù)組件本身)上,驗(yàn)證這個(gè)身份,在這個(gè) demo 項(xiàng)目,給函數(shù)綁定的 displayName 屬性,證明組件身份。 - 要克隆
FormItem
節(jié)點(diǎn),將改變表單單元項(xiàng)的方法 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'
設(shè)計(jì)思想:
FormItem
一定要綁定 displayName 屬性,用于讓<Form>
識別<FormItem />
- 聲明
onChange
方法,通過 props 提供給<Input>
,作為改變 value 的回調(diào)函數(shù)。 FormItem
過濾掉除了input
以外的其他元素。
Input.js:
/* Input 組件, 負(fù)責(zé)回傳value值 */ function Input({ onChange , value }){ return <input className="input" onChange={ (e)=>( onChange && onChange(e.target.value) ) } value={value} /> } /* 給Component 增加標(biāo)簽 */ Input.displayName = 'input'
設(shè)計(jì)思想:
- 綁定 displayName 標(biāo)識
input
。 input
DOM 元素,綁定 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è)置表單數(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é)點(diǎn),混入改變表單單元項(xiàng)的方法 */ 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)
啟動(dòng)項(xiàng)目,查看效果:
點(diǎn)擊提交,我們在輸入框里輸入的內(nèi)容就能顯示在控制臺上。
為了體現(xiàn)出咱們這個(gè)嵌套組件的高可復(fù)用性,我們可以在根組件中隨意添加子項(xiàng):
到此這篇關(guān)于React props全面詳細(xì)解析的文章就介紹到這了,更多相關(guān)React props內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react項(xiàng)目如何使用iconfont的方法步驟
這篇文章主要介紹了react項(xiàng)目如何使用iconfont的方法步驟,這里介紹下如何在項(xiàng)目中配置。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-03-03React中的setState使用細(xì)節(jié)和原理解析(最新推薦)
這篇文章主要介紹了React中的setState使用細(xì)節(jié)和原理解析(最新推薦),前面我們有使用過setState的基本使用, 接下來我們對setState使用進(jìn)行詳細(xì)的介紹,需要的朋友可以參考下2022-12-12探討JWT身份校驗(yàn)與React-router無縫集成
這篇文章主要為大家介紹了JWT身份校驗(yàn)與React-router無縫集成的探討解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06React中useEffect 與 useLayoutEffect的區(qū)別
本文主要介紹了React中useEffect與useLayoutEffect的區(qū)別,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07React?中的?JS?報(bào)錯(cuò)及容錯(cuò)方案
這篇文章主要為大家介紹了React?中的?JS?報(bào)錯(cuò)及容錯(cuò)方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08淺談react.js中實(shí)現(xiàn)tab吸頂效果的問題
下面小編就為大家?guī)硪黄獪\談react.js中實(shí)現(xiàn)tab吸頂效果的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09