从子组件中观察状态与物料UI反应

时间:2017-11-19 23:09:55

标签: reactjs material-ui

React的新功能。只使用create-react-app和Material UI,没有别的。 来自Angular背景。

我无法从兄弟组件进行通信以打开侧边栏。 我将每个部分分成他们自己的文件。 我可以在Header中打开按钮与父App交谈,但无法让父App与孩子LeftSidebar通信。

标题组件

String.Join(",", set.Ins.Select((input, i) => input + set.Outs.Skip(i).First()));

App Component - 从Header接收事件,但不确定如何将动态“观察者”传递给LeftSidebar Component

import React, {Component} from 'react';
import AppBar from 'material-ui/AppBar';
import IconButton from 'material-ui/IconButton';
import NavigationMenu from 'material-ui/svg-icons/navigation/menu';

class Header extends Component {

  openLeftBar = () => {
    // calls parent method
    this.props.onOpenLeftBar();
  }

  render() {
    return (
      <AppBar iconElementLeft={
                <IconButton onClick={this.openLeftBar}>
                  <NavigationMenu />
                </IconButton>
              }
      />
    );
  }
}

export default Header;

LeftSidebar组件 - 无法让它听父App组件 - Angular会使用$ scope。$ watch或$ onChanges

import React, { Component } from 'react';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import RaisedButton from 'material-ui/RaisedButton';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';

// components
import Header from './Header/Header';
import Body from './Body/Body';
import Footer from './Footer/Footer';
import LeftSidebar from './LeftSidebar/LeftSidebar';

class App extends Component {
  constructor() {
    super() // gives component context of this instead of parent this
    this.state = {
      leftBarOpen : false
    }
  }

  notifyOpen = () => {
    console.log('opened') // works
    this.setState({leftBarOpen: true});
    /*** need to pass down to child component and $watch somehow... ***/
  }

  render() {
    return (
      <MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
        <div className="App">

          <Header onOpenLeftBar={this.notifyOpen} />

          <Body />

          <LeftSidebar listenForOpen={this.state.leftBarOpen} />

          <Footer />

        </div>
      </MuiThemeProvider>
    );
  }
}

export default App;

2 个答案:

答案 0 :(得分:2)

放开像“观察者”这样的概念。在React中,只有stateprops。当组件的状态通过this.setState(..)更改时,它将更新render中的所有子项。

您的代码遭受了重复状态的典型反模式。如果标题和兄弟组件都想要访问或更新同一个状态,那么它们属于一个共同的祖先(在你的情况下是App),而不是其他地方。

(为简洁起见,删除/重命名了一些内容)

class App extends Component {
  // don't need `constructor` can just apply initial state here
  state = { leftBarOpen: false }
  // probably want 'toggle', but for demo purposes, have two methods
  open = () => { 
    this.setState({ leftBarOpen: true }) 
  }
  close = () => { 
    this.setState({ leftBarOpen: false }) 
  }
  render() {
    return (
      <div className="App">
        <Header onOpenLeftBar={this.open} />
        <LeftSidebar 
          closeLeftBar={this.close}
          leftBarOpen={this.state.leftBarOpen} 
        />
      </div>
    )
  }
}

现在Header和LeftSidebar根本不需要成为类,只需对props做出反应,并调用prop函数。

const LeftSideBar = props => (
  <Drawer open={props.leftBarOpen}>
    <IconButton onClick={props.closeLeftBar}>
      <NavigationClose />
    </IconButton>  
  </Drawer>
)

现在,只要App中的状态发生变化,无论是谁发起了变更,你的LeftSideBar都会做出适当的反应,因为它只知道最新的道具

答案 1 :(得分:1)

leftBarOpen道具设置为LeftNavBar的内部状态后,您无法再在外部对其进行修改,因为您只读取constructor中仅在组件运行一次时的道具自我初始化。
您可以使用componentWillReceiveProps life cycle method并在收到新道具时分别更新状态。

话虽如此,我认为抽屉不应该对被关闭或开放负责,但应该对其关闭或打开时的外观或内容负责。

抽屉不能自行关闭或打开,与轻型球相同无法打开或关闭球,但开关/按钮可以而且应该。

这是一个小例子来说明我的观点:

const LightBall = ({ on }) => {
  return (
    <div>{`The light is ${on ? 'On' : 'Off'}`}</div>
  );
}

const MySwitch = ({ onClick, on }) => {
  return (
    <button onClick={onClick}>{`Turn the light ${!on ? 'On' : 'Off'}`}</button>
  )
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      lightOn: false
    };
  }

  toggleLight = () => this.setState({ lightOn: !this.state.lightOn });

  render() {
    const { lightOn } = this.state;
    return (
      <div>
        <MySwitch onClick={this.toggleLight} on={lightOn} />
        <LightBall on={lightOn} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>