本文爲入門級別的技術文章以及讀書筆記,大佬請忽略。demo地址:連接javascript
父組件向子組件通信是咱們開發中很是常見,其方式也通常是props屬性最直接方便,固然這裏指的是直接就是父子組件關係的。vue
這裏只但願提示你們一點,若是你有須要,不要直接修改傳入屬性的值,若是你嘗試直接修改會有提示不建議不該該這樣操做,這個變量是隻讀的。若是你切實須要,你能夠在state中接受並改變。java
通信的方式主要有兩種,分別是自定義事件以及回調函數。其中回調函數很是方便,並且能夠拿到執行時的全部狀態;其中自定義事件比較麻煩,咱們通常使用比較少。node
在下面的例子中,我使用的方式是回調函數。react
// listItem.jsx
render() {
const { title } = this.state;
const { click } = this.props;
const title2 = title + "temp";
return (
<h2 onClick={click} onMouseDown={this.down}> {title2} </h2>
);
}
// list.jsx
render() {
const { list } = this.state;
const style = {
height: 30
};
return (
<div style={style}> {list.map(item => ( <ListItem {...item} click={this.click.bind(this)} error={this.error} /> ))} </div> ); } 複製代碼
備註:若是你但願傳遞事件之外側參數,你須要bind綁定傳參或者寫成簡單的箭頭函數。不然,直接寫函數以及參數會編譯成函數直接執行。(好比傳參text,onClick = {text => this.click(text), 或者寫成,onClick={this.click.bind(this,text)}).git
兄弟組件若是是同一個父組件能夠藉助父組件進行通訊,這裏很少描述,這裏分享的是基於事件發佈訂閱機制的實現,咱們藉助nodejs的Events模塊。github
根目錄下建立一個單例的事件,而後導出,爲了不浪費,咱們全局使用一個單例文件events.js。npm
import {EventEmitter} from 'events';
export default new EventEmitter();
複製代碼
// a組件中觸發
import emitter from '../events';
emitter.emit('clickMsg','點擊了')
// b組件監聽
commpontentDidMount(){
this.clickMsg = emitter.on('clickMsg',data => {
console.log(data)
})
}
compontWillUnmount(){
emitter.removeListener(this.clickMsg)
}
複製代碼
備註:事件的執行是否會屢次執行?目前遇到的一個問題,日誌會執行屢次,文件中的說明,Hook a console constructor and forward messages to a callback 。json
跨級組件咱們也能夠認爲是父子組件的疊加,所以可使用多層父子組件的數據傳遞,但這樣比較繁瑣,所以更建議的方式是是經過context的方式,來實現。它的原理也比較簡單,就是基於同一個父組件的前提下,它的任何層級的子組件均可以拿到這個父附件的狀態,並以此進行通信。redux
在以前的官網版本中,一直具備這個功能,不過不建議使用而已,但隨着版本迭代,慢慢發現這個功能具備較大的便利性。
// 定義一個context的全局對象:須要注意的是你在provider的地方使用它的時候也必須符合基本的數據結構
import React from "react";
export const ThemeContext React.createContext({
color: "black",
label: "名字"
});
// 父或者頂層容器設計
import { ThemeContext} from "../themeContext";
constructor(props){
super(props);
this.state = {
theme:{
color:'pink',
label:'你的名字'
}
}
}
render(){
return (
<ThemeContext.Provider value={this.state.theme}>
</ThemeContext.Provider>
)
}
// 任意層級的子組件
import {ThemeContext} from "../../themeContext";
render(){
<ThemeContext.Consumer>
{({ color, label }) => <label style={{ color: color }}>{label}</label>}
</ThemeContext.Consumer>
}
複製代碼
在antd 的form組件中,咱們也看到對應的context的代碼部分:文件地址:連接
// context 設置的默認值
import createReactContext, { Context } from 'create-react-context';
import { ColProps } from '../grid/col';
export interface FormContextProps {
vertical: boolean;
colon?: boolean;
labelAlign?: string;
labelCol?: ColProps;
wrapperCol?: ColProps;
}
export const FormContext: Context<FormContextProps> = createReactContext({
labelAlign: 'right',
vertical: false,
});
// 引入默認的context
import { FormContext } from './context';
// 獲取傳入的屬性,而後解構,經過context provider的方式,state傳值提供給包含在form組件中的組件
render() {
const { wrapperCol, labelAlign, labelCol, layout, colon } = this.props;
return (
<FormContext.Provider value={{ wrapperCol, labelAlign, labelCol, vertical: layout === 'vertical', colon }} > <ConfigConsumer>{this.renderForm}</ConfigConsumer> </FormContext.Provider> ); } 複製代碼
雖然上面的方式提到了各類通信機制,但對於父組件須要主動觸發子組件的某個事件仍是沒有相應的方法。咱們在下面的例子中給出這種需求的場景。好比:在容器組件中,咱們須要主動刷新列表中的數據,而列表組件的數據是本身控制獲取的。
備註:固然有的人會說,既然有如此的需求,爲何不把子組件的數據或者方法經過父組件的屬性傳入,把邏輯在父組件中維護,這樣就沒有這樣的問題。當然是一種很不錯的方法,但也確實存在有些時候,咱們但願子組件更多的具備一些功能,而不重度依賴父組件,只是將本身的事件暴露給父組件。其實這種設計思路在vue中,在開源的ui框架中不少,稱爲自定義事件,咱們除了屬性傳入控制組件以外,還能夠經過組件的自定義事件,事件調用實現改變組件的機制。
class List extends React.Component{
constructor(props){
this.state ={
desc:''
}
}
xxxMethod(){
const desc = Math.random();
this.setState({
desc
})
}
render(){
const {list} = this.props;
return (
<div> <button onClick={this.xxxMethod.bind(this)}>點擊變更標題描述</button> {list.map(text =>(<h2>{text}{desc}</h2>))} </div>
)
}
}
class parent extends React.Component{
refresh(){
// 能夠調用子組件的任意方法
this.refs.List.xxxMethod();
}
//父組件
render (){
return (
<div> <button onClick={this.refresh.bind(this)}>點擊變更標題描述</button> <List ref="List" /> </div>) } } 複製代碼
經過回調事件傳回this,而後賦值給父組件。看上去這段和ref的直接使用沒有任何差異,不過能肯定的是經過這種方式,咱們能準確的拿到子組件的this實例。它爲何是合理存在的呢?由於有些狀況下,咱們經過ref拿到的不是咱們想要的子組件中的屬性或者方法。
class Child extends React.Component{
constructor(props){
super(props);
}
componentDidMount(){
this.props.onRef(this)
}
console(){
console.log('子組件的方法');
}
render(){
return (<div>子組件</div>)
}
}
class Parent extends React.Component{
onRef(comp){
this.child = comp;
}
console(){
this.child.console()
}
render(){
return (
<div> <button onClick={this.console.bind(this)}>點擊執行子組件方法</button> <Child onRef={this.onRef.bind(this)}/> </div> ) } } 複製代碼
經過高階組件的方式,咱們能夠一次性解決相似的需求,而不是每次都手動的寫this的暴露方法。在你須要的位置引入,而後父子組件分別寫上@withRef。
import React from "react";
export default WrappedComponent => {
return class withRef extends React.Component {
static displayName = `withRef(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
render() {
// 這裏從新定義一個props的緣由是:
// 你直接去修改this.props.ref在react開發模式下會報錯,不容許你去修改
const props = {
...this.props
};
// 在這裏把getInstance賦值給ref,
// 傳給`WrappedComponent`,這樣就getInstance能獲取到`WrappedComponent`實例
// 感謝評論區的[yangshenghaha]同窗的完善
props.ref = el => {
this.props.getInstance && this.props.getInstance(el);
this.props.ref && this.props.ref(el);
};
return <WrappedComponent {...props} />; } }; }; 複製代碼
若是你的語法提示錯誤,不支持裝飾器,你能夠經過下面的設置:安裝包依賴,npm install @babel/plugin-proposal-decorators。而後須要在package.json中設置插件的配置:
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
],
"presets": [
"react-app"
]
},
複製代碼
經過本節,咱們熟悉了react基本的父子組件、兄弟組件、跨級組件之間如何進行通信,咱們須要知道的額外一點是,通信指的不只僅是數據的通信,也有事件的通信,好比主動的根據時機去喚起父組件或者子組件的事件,在組件中咱們能夠很方便的經過屬性調用父組件的方法或者傳遞的數據,咱們也須要知道如何將子組件的事件暴露給父組件使用。
其實重點在下一篇,組件抽象,固然就是說的高級組件,只不過此次從《深刻react技術棧》中獲得了更多更全面的啓示。點擊跳轉:react組件抽象