react其組件化的思想使得組件與組件間的通信變的十分必要,下要我們來分別介紹常見的react組件之間的通信方式。
一、父子組件
react是單向數據流,父組件在展示子組件時,可能會傳遞一些數據給到子組件。父組件通過過屬性=值的形式傳遞給子組件數據,子組件通過props參數獲取父組件傳遞過來的數據。而在某些情況下我們也需要子組件向父組件傳遞消息,這時同樣需要用到props,父組件通過props給子組件傳遞一個自定義的回調函數,隨后在子組件中進行接收和調用,這樣子組件的操作就影響了父組件中的數據 。
import React from 'react'; import ReactDOM from 'react-dom'; class SonClassComponent extends React.Component { render() { const { name, age } = this.props; const { btnClick } = this.props; return ( <div> {/* 父傳子:子組件通過props參數來獲取父組件傳遞的數據 */} <h4>父傳子:my name is {name},{age} years old</h4> {/* 子傳父:子組件中調用這個函數 */} <button onClick={btnClick}>+ click </button> </div> ) } } function SonFunComponent(props) { return <h4> 父傳子:my favorite book is {props.book} </h4> } class Father extends React.Component { state = { name: "davina", book: '《The Dream of Red Mansion》', counter: 0 } render() { const { name, book, counter } = this.state; return (<> <h3>當前計數是:{counter}</h3> {/* 這個地方須要注意this指向的問題 */} <button onClick={this.addCounter}>+</button> {/* 父傳子:父組件通過屬性=值傳遞數據 */} {/* 子傳父:還是通過props,父組件給子組件傳遞一個回調函數(自定義) */} <SonClassComponent name={name} age={18} btnClick={this.addCounter} /> <SonFunComponent book={book} /> </>) } addCounter = () => { this.setState({ counter: this.state.counter + 1 }) } } ReactDOM.render(< Father />, document.getElementById('root'))
二、屬性驗證(propTypes)
對於傳遞的數據有時我們需要進行驗證,看是否符合相應的要求,一般它是用在復用性較強的組件上。我們使用propType庫進行驗證時,首先要導入prop-types,其次是 配置特定的propTypes屬性。如果有某個屬性是必須傳遞的,我們可以用propTypes.xxx.isRequired,或者是沒有傳遞props但希望有一個默認值,可以使用類名.defaultProps={}或者是用到靜態屬性也是可以的。
//1.導入
import propTypes from 'prop-types' import React from 'react' import ReactDom from 'react-dom'
class Son extends React.Component { // 類中使用static propTypes = {} 進行檢證
static propTypes = { data: propTypes.string, //data必須是一個字符串
className:propTypes.string.isRequired //設置必傳屬性的值
} // 設置默認值方法一:
static defaultProps = {data: '暫無'} render() { let {className,data} = this.props return <div className={className}>{data}</div> } } // 設置默認值方法二 // Son.defaultProps = {data: '暫無'}
class Father extends React.Component { state = { data: '楊柳回塘,鴛鴦別浦,綠萍漲斷蓮舟路', className: 'box' } render() { let { data, className } = this.state return ( <Son className={className} data={data}></Son> ) } } ReactDom.render(<Father />, document.getElementById('root'))
三、context
對於非父子層級較深的組件進行通信時我們可以使用react提供的一個API即Context,它提供了一種在組件之間共享數據的方式,而不必通過每一層組件,可以共享一些數據,其它的組件都可以從中進行讀取。有新舊兩種寫法。
context它有四個與之相關的api需要我們了解,即:
React.createContext:使用它可以創建一個需要共享的Context對象,如果一個組件訂閱了Context,那么這個組件就會從離自身最近的那個匹配的Provider中讀取到當前context的值,如果沒有找到對應的Provider可以設置一個defaultValue。
Context.Provider:每個Context對象都會返回一個Provider React組件,它允許消費組件訂閱context的變化。Provider接收一個value值,傳遞給消費組件,當Provider的value值發生變化,它內部的所有消費組件都會被重新的渲染。並且一個Provider可以和多個消費組件進行對應,多個Provider也可嵌套使用,里層數據會覆蓋外層數據。
Class.contextType:是掛載在class上的contextType屬性它會被重新賦值一個由React.createContext()創建的Context對象,這樣我們就可以使用this.context來得到使用最近Context上的值;
Context.Consumer:使用這個api,react組件可以訂閱到context的變化,這樣我們可以在函數式組件中完成訂閱context的任務了,強調一點的是它需要函數作為子元素,這個函數接收當前context值,返回一個react節點。
老寫法:聲明上下文前首先我們要用到static chiildContextTypes 聲明上下文中的數據類型,其次是getChildrenContext方法將屬性傳遞給子組件,在子組件中需要使用contextTypes聲明需要用到的屬性的數據類型。
//引入
import propTypes from 'prop-types' import React from 'react' import ReactDom from 'react-dom'
class Son extends React.Component { //3、接收
static contextTypes ={data:propTypes.string} render() { //4、使用
return <div>{this.context.data}</div> } } class Father extends React.Component { //1、聲明
static childContextTypes = { data: propTypes.string, name:propTypes.string } //2、傳遞
getChildContext() { return {data: '楊柳回塘,鴛鴦別浦,綠萍漲斷蓮舟路'} } render() { return <div className=''><Son></Son></div> } } ReactDom.render(<Father />, document.getElementById('root'))
新寫法:首先用到React.createContext創建一個全局的共享對象數據,然后使用Context對象的Provider react組件得到數據,Context.Provider它可以讓每個組件訂閱context的變化 ,如果一個組件訂閱了Context,那么這個組件就會從離自己最近的組件匹配provider進行讀取操作,這樣可以使用數據了。
import React from 'react' import ReactDom from 'react-dom'
// 1、創建
let Context = React.createContext(); class Son extends React.Component { //3.重新賦值掛載
static contextType = Context; render() { //4.用this.context來使用最近Context上的那個值
return <div>{this.context.data}</div> } } class Father extends React.Component { render() { return <div className=''><Son></Son></div> } } ReactDom.render( //2.使用Context.Provider,傳遞數據
<Context.Provider value={{data:'楊柳回塘,鴛鴦別浦,綠萍漲斷蓮舟路'}}>
<Father />
</Context.Provider>, document.getElementById('root'))
四、react中插槽實現
在react中是沒有slot這個概念的,如果要實現vue中slot效果,可以直接通過props進行傳輸。
下面的NavChildren組件中就用到props.children屬性。這個屬性它在每個組件中都有,包含了組件開始和結束標記之間的內容,可以插入文本,表達式或者是節點。this.props.children的值有三種情況,如果當前組件沒有子節點,那它就為undefined,如果有一個子節點,那數據類型為object,如果多個子節點,數據類型為array。當傳入多個子節點,props.children就是一個數組,那就可以通過其下標訪問到子節點,用以控制其出現的位置。但它對順序有極高的要求,又因為子父組件之間的傳遞及組件可以接受任意的props,所以我們可以像NavBar那樣直接進行使用。
// demo.js
import React, { PureComponent } from 'react'; import ReactDOM from 'react-dom'; import './style.css'
class NavChildren extends PureComponent { render() { // this.props.children,用來children,但是它對順序有極高的要求
return ( <div className="nav-item nav-bar">
<div className="nav-left">{this.props.children[0]}</div>
<div className="nav-item nav-center">{this.props.children[1]}</div>
<div className="nav-item nav-right">{this.props.children[2]}</div>
</div> ) } } class NavBar extends PureComponent { render() { return ( <div className="nav-item nav-bar">
<div className="nav-left">{this.props.slotLeft}</div>
<div className="nav-item nav-center">{this.props.slotCenter}</div>
<div className="nav-item nav-right">{this.props.slotRight}</div>
</div> ) } } class App extends PureComponent { render() { return ( <div>
<NavChildren>
<a href='./#'>logo</a>
<div>content</div>
<span>search</span>
</NavChildren>
<NavBar slotLeft={<a href='./#'>logo</a>} slotCenter={<div>content</div>} slotRight={<span>search</span>} />
</div> ) } } ReactDOM.render(< App />, document.getElementById('root')) // styled.css
body {padding: 0;margin: 0;} .nav-bar {display: flex;} .nav-item {height: 44px;line-height: 44px;text-align: center;} .nav-left, .nav-right {width: 15%;background-color: lightpink;} .nav-center {flex: 1;background-color: lightblue;}
五、通信案例
// demo.js import React, { PureComponent } from 'react'; import ReactDOM from 'react-dom'; import propTypes from 'prop-types' import './style.css' class HeaderComponent extends PureComponent { constructor(props) { super(props) // 記錄哪個被選中 this.state = { currentIndex: 0 } } static propTypes = { header: propTypes.array.isRequired } static defaultProps = { header: [] } render() { //解構父組件傳遞過來的header const { header } = this.props; const { currentIndex } = this.state; return ( <div className='total_header' > {header.map((item, index) => { return <div key={item} // 動態添加class className={'item_header ' + (index === currentIndex ? "active" : "")} onClick={this.currentClick.bind(this, index)}> <span>{item}</span></div> })} </div> ) } currentClick(index) { this.setState({ currentIndex: index }) const { currentItemClick } = this.props; //把index傳給currentItemClick currentItemClick(index); } } class App extends PureComponent { constructor(props) { super(props); this.state = { header: ['商品', '評價', '詳情','推薦'], currentHeader: '商品' } } render() { const { header, currentHeader } = this.state; return ( <div> {/* 父傳子:用到props */} <HeaderComponent header={header} currentItemClick={index => this.currentItemClick(index)} /> <h3>{currentHeader}</h3> </div> ) } currentItemClick(index) {//要注意這里的index this.setState({ currentHeader: this.state.header[index] }); } } ReactDOM.render(< App />, document.getElementById('root')) // styled.css .total_header{display: flex;} .item_header{flex:1;text-align: center;margin-top: 10px;} .item_header.active{color:red;} .item_header.active span{border-bottom: 2px solid red;padding:5px;}