React.js:未传递到子属性的父状态值+无法访问Fetch API数据

时间:2017-12-09 07:20:11

标签: javascript reactjs fetch-api

我正在开发一个非常基本的颜色和谐选择器中遇到几个问题。我仍然是React和JSX的初学者。我最初把它放在GitHub上,所以完整的文件就在那里,但我把它移到了Codepen上。

Here is the Codepen

我做了很多评论,很抱歉,如果他们有点多,但希望他们有所帮助。我的问题直到第41行,即DataStore类的displayHarmonies()方法才开始。传递给它的值来自我的App(父)组件:

displayHarmonies(color, harmony) {
    //color and harmony pass in dynamically just fine...this.data will not return anything, not even "undefined"
    console.log(color + " is the color and " + harmony + " is the harmony...and dataStore.displayHarmonies says: " + this.data);

    this.registeredWatchers.map((watcher) => {
        let result = "not green"; //result and resultHex will be determined with an underscore statement that will associate the color & harmony choice (primary + foreign key concept) and will return correct harmony color(s)
        let resultHex = "#HEX";

        appState.harmonyColor = result;
        appState.harmonyHex = resultHex;

        //call to app component's onDataChange() method, where new states will be set using the the appState data we just set in lines 49 and 50
        watcher.onDataChange();
    })
}  

从我的第一条评论中可以看出,唯一没有登录到控制台的部分是this.data,它是在DataStore的构造函数中设置的:

constructor(data) {
    //store that data in the object
    //data is not being received from object instance of dataStore on line 187
    this.data = data;

在第187行,我创建了一个DataStore实例,并向其传递一个名为data的变量。在使用之前,初始化此变量,然后通过Fetch API将其分配给已解析的JSON数据:

let data = [];

//use polyfill for older browsers to do Ajax request
fetch("data/data.json").then((response) => {
//if we actually got something
    if (response.ok) {
        //then return the text we loaded
        return response.text();
    }
}).then((textResponse) => {
    data = JSON.parse(textResponse);
});

如果我在第二次提取.then()方法中控制数据,那么JSON就可以了。只要我尝试在应用程序中的任何其他位置使用data变量,它就不会返回任何内容,如displayHarmonies()方法console.log()中所示。所以这是我的第一个问题,但在我想要达到这个目标之前,我想解决我遇到的另一个问题。

appState对象(在DataStore之前初始化,在fetch语句之下)之后,值设置为result变量,displayHarmonies()运行watcher.onDataChange()(在应用程序中)组件/父级)将harmonyColorharmonyHex状态分配给新的appState值:

onDataChange() {
    console.log("onDataChange() in App called");
    this.setState({
        harmonyColor: appState.harmonyColor,
        harmonyHex: appState.harmonyHex
    })
}

如果我将这些状态记录到控制台,它们是正确的值,所以这不是问题所在。然后我将状态传递给Display子组件以用作属性:

<Display colorChoice={this.state.currentColor} harmonyChoice={this.state.currentHarmony} harmonyColor={this.state.harmonyColor} harmonyHex={this.state.harmonyHex} /> 

然后我在构造函数中设置Display组件状态,并将它们分配给每次应用程序的新呈现时发送给它的props。然后,我使用Display组件的render方法将数据显示在DOM上。奇怪的是,应用程序将显示初始状态(颜色:红色,和谐:直接,和谐色彩:绿色等),但是一旦做出更改,DOM上的数据就不会更新。但是,初始数据以相同的方式加载:将父级的状态传递给子级的属性。我有几个console.log(),似乎证明了为什么这应该有效,但事实并非如此。那么我做错了什么?

谢谢,希望这对一个问题来说不是太多了!

2 个答案:

答案 0 :(得分:2)

我尝试克隆你的回购,但它似乎嵌套在另一个回购中。使用您当前的设置,这可能有效:

在您的App组件中,您可以使用此生命周期方法来获取数据,然后使用接收的数据设置状态:

    componentDidMount(){
      fetch("data/data.json").then((response) => {
    //if we actually got something
    if (response.ok) {
        //then return the text we loaded
        return response.text();
    }
}).then((textResponse) => {
  this.setState({
    data : JSON.parse(textResponse);
  })

});
    }

在return语句中,您可以将数据存储呈现为子项,以便App可以传递这样的数据:

return (
    <div className="App">
      <DataStore data={this.state.data} />
      <h1>Color Harmonies</h1>
        {/* assigns this.colorChosen() & this.harmonyChosen() methods as properties to be called in Picker component */}
        <Picker colorChosen={this.colorChosen.bind(this)} harmonyChosen={this.harmonyChosen.bind(this)}/>
        {/* give Display component props that are dynamically set with states */}
        <Display colorChoice={this.state.currentColor} harmonyChoice={this.state.currentHarmony} harmonyColor={this.state.harmonyColor} harmonyHex={this.state.harmonyHex} />
    </div>
);

然后,您的数据存储应该作为道具接收数据,因此您可以像这样使用它:

displayHarmonies(color, harmony) {
    //color and harmony pass in dynamically just fine...this.data will not return anything, not even "undefined"
    console.log(color + " is the color and " + harmony + " is the harmony...and dataStore.displayHarmonies says: " + this.props.data); //data is received in the properties so you can use it. 
//other code


})

这样做,您还应该能够从DataStore组件的构造函数中删除this.data。

同样在数据存储中,您希望允许它接受这样的道具:

constructor(props){
super(props)
}

答案 1 :(得分:2)

首先是你当前的代码,在帖子的最后,我添加了一个替代解决方案,所以如果这是tl; dr;只是跳到最后的片段:)

第一个评论是关于你希望传递给data的{​​{1}}变量,nl(我遗漏了一些部分,因为它们与讨论无关)

DataStore

您要在fetch调用的let data = []; fetch("data/data.json").then(( response ) => { data = JSON.parse( response.text() ); }); //... later down the code var store = new DataStore(data); 承诺链中重新分配data变量。虽然赋值似乎有效,但现在then上的数据将是一个空数组,全局变量store.data现在将包含已解析的data。您可能只需要输入刚刚解析过的数据(但在我的示例中,我甚至没有包含response.text(),因此这仅供将来参考)

在你的CodePen中,你似乎混合了道具和您的DataStore组件的状态。这本质上是一个无操作,除非你真的知道你在做什么,否则你不应该混合它们。另请注意,通过在Display生命周期方法中调用this.setState,应用会自动重新渲染超过需要的内容。我指的是这段代码:

componentWillReceiveProps

但是你会像这样呈现:

componentWillReceiveProps(nextProps) {
  this.setState({
    color: nextProps.colorChoice,
    harmony: nextProps.harmonyChoice,
    harmonyColor: nextProps.harmonyColor,
    harmonyHex: nextProps.harmonyHex
  });
}

在这里,您应该删除render() { return ( <div> {/* these aren't changing even though states are being set */} <p><b>Color:</b> {this.state.color}</p> <p><b>Harmony:</b> {this.state.harmony}</p> <p><b>Harmony Color(s):</b> {this.state.harmonyColor} ({this.state.harmonyHex})</p> </div> ) } 方法,并在componentWillReceiveProps传递这些值时从this.props呈现值。

替代解决方案

正如评论中所提到的,您的代码目前正在做的事情比在父组件和子组件之间传递状态要多得多。

您应该记住的一件事是,当组件状态发生变化时,react会自动重新渲染组件。当它发现虚拟DOM与真实DOM存在差异时,它将自动替换这些组件。

从这个意义上说,你的App没有必要。根据您希望如何管理状态,组件将对这些更改做出反应。

由于您的应用程序使用组件状态(适用于小型应用程序,一旦您想要迁移到更大的应用程序,您可能希望继续使用Redux或MobX),这是您唯一需要做的事情,是确保您设置正确的组件状态以触发渲染。

例如,我以更清洁的方式重新编写代码:

DataStore
const Choice = ({ header, values, onChange, activeValue }) => {
  return <ul>
    <li><h1>{ header }</h1></li>
    { values.map( (value, key) =>	<li 
      key={key+value}
      className={classNames( { active: value === activeValue, item: true } )} 
      onClick={() => onChange( value )}>{ value }</li> ) }
  </ul>
};

const colors = ['red', 'green', 'black', 'blue', 'yellow'];
const harmonies = ['direct', 'split', 'analogous'];

class App extends React.Component {
  constructor(...args) {
    super(...args);
    this.state = {
      activeColor: undefined,
      activeHarmony: undefined
    };
  }
  onColorChanged( color ) {
    this.setState({ activeColor: color });
  }
  onHarmonyChanged( harmony ) {
    this.setState({ activeHarmony: harmony });
  }
  render() {
    let { activeColor, activeHarmony } = this.state;
    return <div>
      <Choice 
        header="Choose color" 
        values={colors} 
        activeValue={activeColor} 
        onChange={(...args) => this.onColorChanged(...args)} />
      <Choice 
        header="Choose harmony" 
        values={harmonies} 
        activeValue={activeHarmony}
        onChange={(...args) => this.onHarmonyChanged(...args)} />
    </div>;
  }
}

ReactDOM.render( <App />, document.querySelector('#container'));
h1 { margin: 0; padding: 0; }
ul {
  list-style-type: none;
}
.item {
  cursor: pointer;
  padding: 5px;
}
.active { background-color: lightgreen; }

现在,此示例代码中有一些内容可能需要一些解释。首先,这个代码有2个组件类型,1个名为<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script> <div id="container"></div>的表示组件是无状态的,还有一个名为Choice的容器组件将它的状态委托给它的子组件。

有关容器和更多信息的更多信息表示组件可以在Dan Abramov(redux creator)的blog上找到

上述概念的本质就是这个,App组件负责状态,并与它的子项共享。因此,需要在App组件上进行所有状态更改。正如您在渲染中看到的那样,App只是沿着状态传递:

App

render() { let { activeColor, activeHarmony } = this.state; return <div> <Choice header="Choose color" values={colors} activeValue={activeColor} onChange={(...args) => this.onColorChanged(...args)} /> <Choice header="Choose harmony" values={harmonies} activeValue={activeHarmony} onChange={(...args) => this.onHarmonyChanged(...args)} /> </div>; } 将更改处理程序传递给App组件,当选择发生时可以调用该组件,这会转发到Choice,状态发生变化,以及app re -renders,允许App组件更新它的元素。

Choice

根据传递给它的道具,const Choice = ({ header, values, onChange, activeValue }) 组件可以决定渲染时哪个是活动项。如你所见,道具被破坏了。 ChoiceheadervaluesonChange都是组件activeValue上的所有属性,但为了节省时间,我们可以将这些值分配给变量并在渲染中使用它们。