react通訊

麥兜兜008發表於2019-03-22

梳理父元件向子元件、子元件向父元件、跨級元件間、非巢狀元件間通訊方式以及如何用redux進行資料管理

1、父元件向子元件通訊

1)傳遞props

最常見的元件通訊方式,一般父元件通過props向子元件傳送資料 父元件App.js

import React, { Component } from "react";
import { render } from "react-dom";
import Sub from "./sub";

class App extends Component {
  render() {
    return <Sub title="hello, react" />;
  }
}

render(<App />, document.getElementById("root"));

複製程式碼

子元件Sub.js

import React from "react";

const Sub = props => {
  return <h1>{props.title}</h1>;
};

export default Sub;

複製程式碼

2)呼叫子元件方法

父元件直接呼叫子元件的方法,也可以達到通訊的目的 父元件App.js

import React, { Component } from "react";
import { render } from "react-dom";
import Sub from "./sub";

class App extends Component {
  hideSub() {
    this.refs.subComponent.hide();
  }

  render() {
    return (
      <div>
        <Sub ref="subComponent" />
        <button onClick={this.hideSub.bind(this)}>隱藏子元件</button>
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));
複製程式碼

子元件Sub.js

import React, { Component } from "react";

export default class Sub extends Component {
  state = {
    show: true
  };

  hide() {
    this.setState({
      show: false
    });
  }

  render() {
    return this.state.show ? <h1>hello, react</h1> : null;
  }
}
複製程式碼

2、子元件向父元件通訊

1)回撥父元件通過props傳遞過來的方法

父元件App.js

import React, { Component } from "react";
import { render } from "react-dom";
import Sub from "./sub";

class App extends Component {
  state = {
    showSubComponent: true
  };

  showSub() {
    this.setState({
      showSubComponent: true
    });
  }

  hideSub() {
    this.setState({
      showSubComponent: false
    });
  }

  render() {
    return (
      <div>
          {
              this.state.showSubComponent 
              ? <Sub ref="subComponent" hideSub={this.hideSub.bind(this)} /> 
              : null
          }
          
          <button onClick={this.showSub.bind(this)}>顯示子元件</button>
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

複製程式碼

子元件Sub.js

import React, { Component } from "react";

export default class Sub extends Component {
  render() {
    let { hideSub } = this.props;
    return (
      <div>
        hi,react <button onClick={() => hideSub()}>隱藏</button>
      </div>
    );
  }
}

複製程式碼

2)自定義事件

event.js

import { EventEmitter } from "events";
export default new EventEmitter();

複製程式碼

父元件App.js

import React, { Component } from "react";
import { render } from "react-dom";
import emitter from "./event";
import Sub from "./sub";

const list = ["apple", "banana"];

class App extends Component {
  state = {
    msg: ""
  };

  componentDidMount() {
    // 宣告自定義事件
    this.eventEmitter = emitter.on("subClick", msg => {
      this.setState({
        msg
      });
    });
  }

  render() {
    return (
      <div>
        {this.state.msg}
        <ul>
          {list.map((item, index) => (
            <Sub key={index} name={item} />
          ))}
        </ul>
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

複製程式碼

子元件Sub.js

import React, { Component } from "react";
import emitter from "./event";

export default class Sub extends Component {
  handleClick() {
    emitter.emit("subClick", this.props.name);
  }

  render() {
    return <li onClick={this.handleClick.bind(this)}>{this.props.name}</li>;
  }
}

複製程式碼

3、跨級元件間通訊

1)通過props層層傳遞,中間的元件也需要新增對應的props屬性,傳遞給下一層

import React, { Component } from "react";
import { render } from "react-dom";

const ThemeButton = props => <button style={{color: props.theme}}>{props.theme}</button>

// 中間元件,將theme傳遞給ThemeButton
const Toolbar = props => <ThemeButton theme={props.theme}/>

class App extends Component {
  render() {
    return <Toolbar theme="red"/>;
  }
}

render(<App />, document.getElementById("root"));

複製程式碼

2)使用context

import React, { Component } from "react";
import { render } from "react-dom";

// 建立一個theme context,預設值為light
const { Provider, Consumer } = React.createContext("light");

const ThemeButton = props => {
  return <Consumer>{theme => <button {...props}>{theme}</button>}</Consumer>;
};

// 中間元件
const Toolbar = props => <ThemeButton />;

class App extends Component {
  render() {
    return (
      <Provider value="dark">
        <Toolbar />
      </Provider>
    );
  }
}

render(<App />, document.getElementById("root"));

複製程式碼

4、非巢狀元件間通訊

就是沒有包含關係的元件,比如兄弟元件 1)自定義事件 父元件App.js

import React, { Component } from "react";
import { render } from "react-dom";
import Sub from "./sub";
import Sub2 from "./sub2";

class App extends Component {
  render() {
    return (
      <div>
        <Sub />
        <Sub2 />
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

複製程式碼

子元件Sub.js

import React, { Component } from "react";
import emitter from "./event";

export default class Sub extends Component {
  handleClick() {
    emitter.emit("siblingsMsg", "元件一");
  }

  render() {
    return (
      <div>我是元件一:<button onClick={this.handleClick.bind(this)}>點我點我</button></div>
    )
  }
}

複製程式碼

子元件Sub2.js

import React, { Component } from "react";
import emitter from "./event";

export default class Sub2 extends Component {
  state = {
    msg: ''
  }

  componentDidMount() {
    // 宣告自定義事件
    this.eventEmitter = emitter.on("siblingsMsg", msg => {
      this.setState({
        msg
      });
    });
  }

  render() {
    return (
        <div>我是元件二:收到來自{this.state.msg}的資訊</div>
    );
  }
}

複製程式碼

5、使用redux進行資料管理

入口檔案index.js

import React, { Component } from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { createLogger } from "redux-logger";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
import App from "./components/app";

const middleware = [thunk];
if (process.env.NODE_ENV !== "production") {
  middleware.push(createLogger());
}

const store = createStore(rootReducer, applyMiddleware(...middleware));

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

複製程式碼

components/app.js

import React, { Component } from "react";
import { connect } from "react-redux";
import Sub from "./sub";
import { changeList, toggleItem } from "../actions";

class App extends Component {
  componentWillMount() {
    this.props.changeList();
  }

  render() {
    const { list, toggle } = this.props;
    return (
      <ul>
        {list &&
          list.map((item, index) => (
            <Sub
              key={item.id}
              {...item}
              doClick={() => toggle(item.id)}
            />
          ))}
      </ul>
    );
  }
}

const mapStateToProps = state => ({
  list: state
});

const mapDispatchToProps = dispatch => ({
  toggle: id => dispatch(toggleItem(id)),
  changeList: () => dispatch(changeList())
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

複製程式碼

components/sub.js

import React, { Component } from "react";

export default class Sub extends Component {
  render() {
    const { id, name, removed, doClick } = this.props;

    return (
      <li
        onClick={doClick}
        style={{ textDecoration: removed ? "line-through" : "none" }}
      >
        {name}
      </li>
    );
  }
}

複製程式碼

actions/actionTypes.js

export const CHANGE_LIST = "CHANGE_LIST";
export const TOGGLE_ITEM = "TOGGLE_ITEM";

複製程式碼

actions/index.js

import * as TYPES from "./actionTypes";

// mock data
import _list from "./list.json";

// 獲取列表資料
const receiveList = list => ({
  type: TYPES.CHANGE_LIST,
  list
});
export const changeList = () => dispatch => {
  setTimeout(() => {
    dispatch(receiveList(_list));
  }, 100);
};

//切換子項顯示
export const toggleItem = id => ({
  type: TYPES.TOGGLE_ITEM,
  id
});

複製程式碼

list.json

[
  {
    "id": 1,
    "name": "Apple",
    "removed": false
  },
  {
    "id": 2,
    "name": "Banana",
    "removed": false
  },
  {
    "id": 3,
    "name": "Orange",
    "removed": true
  }
]

複製程式碼

reducers/index.js

import * as TYPES from "../actions/actionTypes";

const list = (state = [], action) => {
  switch (action.type) {
    case TYPES.CHANGE_LIST:
      return action.list;
    case TYPES.TOGGLE_ITEM:
    console.log("")
      return state.map(item => {
        return item.id === action.id ? { ...item, removed: !item.removed } : item;
      });
    default:
      return state;
  }
};

export default list;

複製程式碼

##6、業界其他資料管理方案,例如,mobx

demo使用庫的版本如下:

react通訊

##總結 react通訊方式有較多選擇,需要根據具體的業務選擇合適的資料傳遞方式,當互動較為複雜,共享資料多,引入redux是明智的決定

相關文章