React:这是使用道具和状态的好方法

时间:2016-10-30 19:05:42

标签: javascript reactjs

我终于得到了一个实际有效的响应式设置。但我非常希望得到一些关于这是一个好的设置和数据流的反馈。

我做了以下事情:

App.js

import React, { Component } from 'react';
import './App.css';
import AB_eval from './components/AB_eval';

class App extends Component {
  constructor() {
    super();
    this.updateDB = this.updateDB.bind(this);
    // getInitialState
    this.state = {
      DB: []
    };
  }

  updateDB(k, bar) {
      const DB = {...this.state.DB}
      DB[k] = bar;
      this.setState({ DB });
  }

  render() {
    return (
      <div className="App">

        <AB_eval A={2} B={3} updateDB={this.updateDB} />

      </div>
    );
  }
}

export default App;

AB_eval.js

import React, { Component } from 'react';
import Slider from 'react-rangeslider';

class AB_eval extends Component {
  constructor(props, context){
    super(props, context);

    this.updateDB = this.updateDB.bind(this);
    this.refreshAB = this.refreshAB.bind(this);

    let {A, B} = this.props;
    let AB = A * B;
    this.state = {
      A: A,
      B: B,
      AB: AB
    }
  }

  componentDidMount () {
    this.updateDB();
  }

  refreshAB () {
    const AB = this.state.A * this.state.B;
    this.setState({
      AB: AB
    });
  }

  updateDB () {
    const bar = {
        A: this.state.A,
        B: this.state.B,
        AB: this.state.AB
      }
    this.props.updateDB(0, bar) // updating the App's state
  }

  render() {
    let {A, B, AB} = this.state;
    return (
      <div>
        <h1>AB_eval: {AB}</h1>


        <h2>A</h2>
        <p>A: {A} B: {B} AB:{AB}</p>
        <Slider min={1} max={4} step={1}
          value={A}
          onChange={ (value)=> {
            this.setState({
              A: value
            });
            this.refreshAB()
            this.updateDB() }
          }
        />

        <h2>B</h2>
        <p>A: {A} B: {B} AB:{AB}</p>
        <Slider min={1} max={4} step={1}
          value={B}
          onChange={ (value)=> {
            this.setState({
              B: value
            });
            this.refreshAB()
            this.updateDB() }
          }
        />
      </div>
    )
  }
}

AB_eval.propTypes = {
  A: React.PropTypes.number.isRequired,
  B: React.PropTypes.number.isRequired,
  updateDB: React.PropTypes.func.isRequired
};

export default AB_eval;

接下来是添加C滑块 - 因此它将成为ABC_eval。但首先我需要知道:

  • App和AB_eval之间的数据流是一种好方法吗?

  • 将内容分解为更多组件是否有意义?

更新

我意识到在setState组件中使用AB_eval在循环遍历状态之后并不是一个好主意。

我将代码更新为以下内容,但它不起作用..想想我错过了几件事情。

伪data.js

module.exports = {
  one:  {A: 1,
      B: 2,
      AB: 2
      },
  two: { A: 3,
        B: 4,
        AB: 12
      }
};

App.js

import React, { Component } from 'react';
import './App.css';
import AB_eval from './components/AB_eval';
import dummyData from './dummy-data';

class App extends Component {
  constructor() {
    super();
    this.updateDB = this.updateDB.bind(this);

    // getInitialState
    this.state = {
      DB: dummyData
    };
  }

  updateDB(key, bar) {
      const DB = {...this.state.DB}
      DB[key] = bar;
      this.setState({ DB });
  }

  render() {
    return (
      <div className="App">
        <ul className="list-of-ABs">
            { Object
              .keys(this.state.DB)
              .map(key =>
              <AB_eval
                key = {key}
                ID = {key}
                Data = {this.state.DB[key]}                
                updateDB={this.updateDB} />
                )
            }
          </ul>
      </div>
    );
  }
}

export default App;

AB_eval.js

import React, { Component } from 'react';
import Slider from 'react-rangeslider';

class AB_eval extends Component {
  constructor(props, context){
    super(props, context);

    this.updateDB = this.updateDB.bind(this);

    // Trying to make these variable instance variables
    const {ID, Data} = this.props;

  }

  updateDB () {
    this.props.updateDB(this.ID, this.Data) // updating the App's state
  }

  render() {
    console.log(this.Data);
    let {A, B, AB} = this.Data;
    return (
      <div>
        <h1>AB_eval: {this.ID}</h1>

        <h2>A</h2>
        <p>A: {A} B: {B} AB:{AB}</p>
        <Slider min={1} max={4} step={1}
          value={A}
          onChange={ (value)=> {
              this.A = value;
              this.AB = this.A * this.B;
              this.updateDB();
            }
          }
        />

        <h2>B</h2>
        <p>A: {A} B: {B} AB:{AB}</p>
        <Slider min={1} max={4} step={1}
          value={B}
          onChange={ (value)=> {
              this.B = value;
              this.AB = this.A * this.B;
              this.updateDB();
            }
          }
        />
      </div>
    )
  }
}

AB_eval.propTypes = {
  ID: React.PropTypes.string.isRequired,
  Data: React.PropTypes.object.isRequired,
  updateDB: React.PropTypes.func.isRequired
};

export default AB_eval;

(不确定我是否在正确的位置使用constlet

在@Pineda和一些尝试的帮助下,我发现以下解决方案按照我的意愿工作:

  

SCR / App.js

import React, { Component } from 'react';
import './App.css';
import ABEval from './components/ABEval';
import dummyData from './dummy-data';

class App extends Component {
  constructor() {
    super();
    this.updateDB = this.updateDB.bind(this);

    // getInitialState
    this.state = {
      DB: dummyData
    };
  }

  updateDB(key, bar) {
      const DB = {...this.state.DB}
      DB[key] = bar;
      this.setState({ DB });
  }

  render() {
    return (
      <div className="App">
        <ul className="list-of-ABs">
            { Object
              .keys(this.state.DB)
              .map(key =>
              <ABEval
                key={key}
                ID={key}
                Data={this.state.DB[key]}
                updateDB={this.updateDB} />
                )
            }
          </ul>
      </div>
    );
  }
}

export default App;
  

SCR /伪data.js

module.exports = {
  one:  {A: 1,
      B: 2,
      AB: 2
      },
  two: { A: 3,
        B: 4,
        AB: 12
      }
};
  

SCR /组件/ ABEval.js

import React, { Component } from 'react';
import XSlider from './XSlider';

class AB_eval extends Component {
  constructor(props, context){
    super(props, context);
    console.log(`${this.props.ID}: Constructed`);
  }

  componentDidMount(){
    console.log(`${this.props.ID}: Mounted`);
  }

  render() {
    console.log(`${this.props.ID}: rendered`)
    const { Data, ID } = this.props;
    const { A, B, AB } = Data;
    return (
      <div>
        <h1>ABEval: {ID}</h1>
        <p>A: {A} B: {B} AB:{AB}</p>

        <XSlider
          title={'A'}
          value={A}
          valueHandler={
            (val)=> this.props.updateDB(ID, {A: val, B: B, AB: val*B} )}
        />

        <XSlider
          title={'B'}
          value={B}
          valueHandler={
            (val)=> this.props.updateDB(ID, {B: val, A: A, AB: val*A} )}
        />
      </div>
    )
  }
}

AB_eval.propTypes = {
  ID: React.PropTypes.string.isRequired,
  Data: React.PropTypes.object.isRequired,
  updateDB: React.PropTypes.func.isRequired
};

export default AB_eval;
  

SCR /组件/ XSlider.js

import React from 'react';
import Slider from 'react-rangeslider';

export default ({title, value, valueHandler}) => {
  return (
    <div>
      <h2>{title}</h2>
      <Slider min={1} max={4} step={1} value={value} onChange={valueHandler} />
    </div>
  );
}

3 个答案:

答案 0 :(得分:1)

根据您提供的信息,很难做出许多重新分解建议,但数据流看起来还不错。

最新AB_eval.js中的问题:

const {ID, Data} = this.props;  // block scoped, DOES NOT create class members
                                // referencable by this.ID or this.DATA
                                // outside the scope of its containing block
  

const是块作用域,所以你的内部道具的解构   构造函数只会在其中创建一个可用的ID和Data值   构造函数块。

     

这打破了后来this.IDthis.Data的引用(在   updateDB()方法)。引用this.Athis.ABthis.B和   this.updateDB方法中的render()也将被破坏。修理   这个,我建议在你的块范围内解构道具   render和onChange handler(s)。

this.AB = this.A * this.B;  // state should be treated as immutable
                            // and since props are propogated by changes in state
                            // mutating them is ill-advised
  

尝试直接在this.props.Data中设置值   onChange方法有效地改变应该被视为什么的方法   不可变的。

     

使用ES6 / ES7可以通过几种方法确保无突变   表示法,一个使用Object.assign,另一个使用Spread syntax notation

<强>解决方案

AB_eval.js:

import React, { Component } from 'react';
import Slider from 'react-rangeslider';

class AB_eval extends Component {
  constructor(props, context){
    super(props, context);

    this.onChangeA = this.onChangeA.bind(this);
    this.onChangeB = this.onChangeB.bind(this);
  }

  onChangeA (value) {
    const {ID, Data, updateDB} = this.props;
    updateDB(ID, {
      ...Data,
      A: value,
      AB: value * Data.B
    });
  }

  onChangeB (value) {
    const {ID, Data, updateDB} = this.props;
    updateDB(ID, {
      ...Data,
      B: value,
      AB: value * Data.B
    });
  }

  render() {
    const {A, B, AB} = this.props.Data;
    return (
      <div>
        <h1>AB_eval: {this.props.ID}</h1>

        <h2>A</h2>
        <p>A: {A} B: {B} AB:{AB}</p>
        <Slider min={1} max={4} step={1}
          value={A}
          onChange={this.onChangeA}
        />

        <h2>B</h2>
        <p>A: {A} B: {B} AB:{AB}</p>
        <Slider min={1} max={4} step={1}
          value={B}
          onChange={this.onChangeB}
          }
        />
      </div>
    )
  }
}

AB_eval.propTypes = {
  ID: React.PropTypes.string.isRequired,
  Data: React.PropTypes.object.isRequired,
  updateDB: React.PropTypes.func.isRequired
};

export default AB_eval;
  • 单个updateDB方法被两个onChange处理程序(分别为onChangeAonChangeB)替换,以处理A和B的滑块情况。
  • 道具在这些处理程序中以及在渲染函数中相应地进行了解构,注意使用扩展语法来创建更新的对象,该对象不会改变现有的this.Data.props
  • onChange处理程序不再使用您之前使用的胖箭头表示法,因此必须将处理程序绑定到this

关于重构的更新:
您可以将Slider代码提取到一个功能组件中(我已经添加了一个文件夹结构作为建议):

  

./组件/ AB_eval_slider.js

import React from 'react';

export default ({key, db, handler}) => {
  return (
    <div>
      <h2>{key}</h2>
      <p>A: {db.A} B: {db.B} AB:{db.AB}</p>
      <Slider min={1} max={4} step={1} value={key} onChange={handler} />
    </div>
  );
}

因此,您还必须编辑我建议的 AB_eval.js 以包含:
import AB_eval_Slider from './components/AB_eval_slider';

,渲染方法现在是:

render() {
  return (
    const { Data } = this.props;
    <div>
      <h1>AB_eval: {this.ID}</h1>
      <AB_eval_Slider key={'A'} db={Data}, handler={this.onChangeA} />
      <AB_eval_Slider key={'B'} db={Data}, handler={this.onChangeB} />
    </div>
  );
}

答案 1 :(得分:1)

老实说,我无法理解你的代码是什么,但是从代码示例和你的问题是 我能想到的是:

dummyData.js

export default {
  one:  {A: 1,
    B: 2,
    AB: 2
  },
  two: { A: 3,
    B: 4,
    AB: 12
  }
};

App.js

import React, { Component } from 'react';
import './App.css';
import dummyData from './dummyData';
import AB_eval from './components/AB_eval';

class App extends Component {
  constructor() {
    super();

    // getInitialState
    this.state = dummyData;

    this.updateDB = (id, value) => this.setState({ id: value });

  }

 render() {
   return (
    <div className="App">
      <ul className="list-of-ABs">
          { Object
            .keys(this.state)
            .map(key =>
              <AB_eval
                key = {key}
                ID = {key}
                Data = {this.state[key]}            
                updateDB={this.updateDB} />
            )
          }
        </ul>
     </div>
    );
  }
}

export default App;

如果您只在应用的状态下拥有key: value存储空间,那么您不需要DB密钥,只需将状态设置为key: value存储空间即可在this.state = dummyData之上,我认为状态树不那么深入,更难以思考和操作它。

您可以在此处使用箭头功能来“绑定”this,例如此处this.updateDB = (id, value) => this.setState({ id: value });。这样您就不需要先定义一个函数,然后将this“绑定”到它中。代码越来越少,代码通常更少,我猜想。

AB_eval.js

import React, { Component } from 'react';
import Slider from 'react-rangeslider';

class AB_eval extends Component {

  constructor(props, context){
    super(props, context);

    this.updateDB = (type, value) => {
      const { ID, DATA } = this.props
      switch(type) {
        case "A":
          this.props.updateDB(
            ID, 
            { ...DATA, A: value, AB: (value * DATA.B) }
          );
          break;
        case "B":
          this.props.updateDB(
            ID, 
            { ...DATA, B: value, AB: (DATA.A * value) }
          );
          break;
        default:
          this.props.updateDB(
            ID, DATA
          );
          break;
      }
    };

  }

  componentDidMount () {
    this.updateDB();
  }

  render() {
    const {A, B, AB} = this.props.DATA;
    return (
      <div>
        <h1>AB_eval: {AB}</h1>

        <h2>A</h2>
        <p>A: {A} B: {B} AB:{AB}</p>
        <Slider min={1} max={4} step={1}
          value={A}
          onChange={ (value)=> this.updateDB("A", value) }
          }
        />

        <h2>B</h2>
        <p>A: {A} B: {B} AB:{AB}</p>
        <Slider min={1} max={4} step={1}
          value={B}
          onChange={ (value)=> this.updateDB("B", value) }
        />
      </div>
    )
  }
}

AB_eval.propTypes = {
  ID: React.PropTypes.string.isRequired,
  DATA: React.PropTypes.object.isRequired,
  updateDB: React.PropTypes.func.isRequired
};

export default AB_eval;

首先想到我能想到你的AB_eval组件,它不需要state它自己的组件。因为所有状态的内容都可以通过props获得。正如在React documentation上所说,你应该:

  

找出应用程序所需状态的绝对最小表示形式,并按需计算所需的其他所有内容。

将状态放在一个位置,通常位于组件层次结构顶部的组件中,这使得理解您的应用程序变得更加容易。

我认为React's文档网站上的这个awsome material可以帮助您在React创建应用时获得很多帮助。

以下是关于constlet here on youtube的精彩教程。但简而言之,只要您不需要更改变量,就可以使用const。将其视为constant变量。最好使用constant将值定义为const,这样您就不会更改它。仅在以后需要更改变量时才使用let

答案 2 :(得分:0)

在您的示例中,没有必要在updateDB组件中执行App。您可以在updateDB中执行AB_eval操作。您可以在A构造函数中初始化BAB_eval,而不是从App组件获取,因为它们是常量。