React.js:父状态值未传递到子属性 + 无法访问获取 API 数据
React.js: Parent state values not passing into child properties + Fetch API data cannot be accessed
我在开发一个非常基本的色彩和谐选择器时遇到了几个问题。我仍然是 React 和 JSX 的初学者。我最初把它放在 GitHub 上,所以完整的文件都在那里,但我把它移到了 Codepen 上。
我发表了很多评论,很抱歉,如果他们有点多,但希望他们有所帮助。我的问题直到第 41 行才开始,DataStore class 的 displayHarmonies() 方法。传递给它的值来自我的应用程序(父)组件:
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);
});
如果我在第二个 fetch .then()
方法中调出数据,JSON 会正常返回。一旦我尝试在应用程序的其他任何地方使用 data
变量,它 returns 什么都没有,如 displayHarmonies()
方法的 console.log()
所示。所以这是我的第一个问题,但在开始之前,我想解决我遇到的另一个问题。
在 appState
对象(在 DataStore 之前初始化,在 fetch 语句下)值设置为 result
变量后,displayHarmonies()
运行 watcher.onDataChange()
(在App component/parent) 其中 harmonyColor
和 harmonyHex
状态被分配给新的 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
组件状态,将它们分配给随应用程序的每个新再现一起发送给它的道具。然后,我使用 Display
组件的渲染方法将数据显示到 DOM 上。奇怪的是,应用程序会显示初始状态(颜色:红色、和谐:直接、和谐颜色:绿色等),但一旦发生更改,DOM 上的数据就不会显示更新。不过,初始数据的加载方式相同:将父级的状态传递到子级的属性中。我有几个 console.log()
s 似乎证明了为什么这应该有效,但是,它没有。那我做错了什么?
谢谢,希望一个问题不会太多!
我尝试克隆您的存储库,但它似乎嵌套在另一个存储库中。使用您当前的设置,这可能有效:
在您的 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>
);
然后,你的数据存储应该接收数据作为 prop,所以你可以像这样使用它:
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)
}
首先是您当前的代码,在 post 的末尾,我添加了一个替代解决方案,所以如果这是 tl;dr;直接跳到最后的片段:)
首先要注意的是您希望传递给 DataStore
的 data
变量,nl(我省略了一些部分,因为它们与讨论无关)
let data = [];
fetch("data/data.json").then(( response ) => {
data = JSON.parse( response.text() );
});
//... later down the code
var store = new DataStore(data);
在这里,您将在 fetch 调用的 then
承诺链中重新分配 data
变量。虽然赋值似乎有效,但现在 store.data
上的数据将是一个空数组,全局变量 data
现在将包含已解析的 response.text()
。您可能应该只推送刚刚解析的数据(但在我的示例中,我什至没有包括 DataStore
所以这仅供将来参考)
在您的 CodePen 中,您似乎混合了 Display
组件的道具和状态。这本质上是一个空操作,除非你真的知道自己在做什么,否则你不应该混合使用它们。另请注意,通过在 componentWillReceiveProps
生命周期方法中调用 this.setState
,应用程序将自动重新呈现超出需要的部分。我指的是这段代码:
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
渲染值,因为你从 App
.
传递这些值
备选方案
如评论中所述,您的代码目前在父子组件之间传递状态时所做的工作比它应该做的要多得多。
您应该记住的一件事是,当组件状态发生变化时,React 将自动重新渲染组件。当它发现虚拟 DOM 与真实 DOM 有差异时,它会自动替换那些组件。
从这个意义上说,您的 DataStore
是没有必要的。根据您希望如何管理状态,组件将对这些更改做出反应。
由于您的应用程序使用组件状态(这适用于小型应用程序,一旦您想迁移到更大的应用程序,您可能会想迁移到 Redux 或 MobX 之类的东西),您唯一需要做的就是要做的是确保设置正确的组件状态以触发渲染。
例如,我以更简洁的方式重写了您的代码:
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; }
<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>
现在,此示例代码中的某些内容可能需要一些解释。其一,此代码有 2 种组件类型,一种称为 Choice
的无状态表示组件,以及一种称为 App
的容器组件,它将其状态委托给它的子组件。
有关容器和展示组件的更多信息可以在 Dan Abramov(redux 创建者)blog
上找到
上述概念的本质就是这样,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
,状态更改,并且应用程序重新呈现,允许 Choice
组件更新它的元素。
const Choice = ({ header, values, onChange, activeValue })
根据传递给它的道具,Choice
组件可以决定渲染时哪个是活动项目。如您所见,道具已被破坏。 header
、values
、onChange
和 activeValue
都是组件 props
上的属性,但是为了节省时间,我们可以将这些值赋值给一个变量并在渲染中使用它们。
我在开发一个非常基本的色彩和谐选择器时遇到了几个问题。我仍然是 React 和 JSX 的初学者。我最初把它放在 GitHub 上,所以完整的文件都在那里,但我把它移到了 Codepen 上。
我发表了很多评论,很抱歉,如果他们有点多,但希望他们有所帮助。我的问题直到第 41 行才开始,DataStore class 的 displayHarmonies() 方法。传递给它的值来自我的应用程序(父)组件:
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:
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);
});
如果我在第二个 fetch .then()
方法中调出数据,JSON 会正常返回。一旦我尝试在应用程序的其他任何地方使用 data
变量,它 returns 什么都没有,如 displayHarmonies()
方法的 console.log()
所示。所以这是我的第一个问题,但在开始之前,我想解决我遇到的另一个问题。
在 appState
对象(在 DataStore 之前初始化,在 fetch 语句下)值设置为 result
变量后,displayHarmonies()
运行 watcher.onDataChange()
(在App component/parent) 其中 harmonyColor
和 harmonyHex
状态被分配给新的 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
组件状态,将它们分配给随应用程序的每个新再现一起发送给它的道具。然后,我使用 Display
组件的渲染方法将数据显示到 DOM 上。奇怪的是,应用程序会显示初始状态(颜色:红色、和谐:直接、和谐颜色:绿色等),但一旦发生更改,DOM 上的数据就不会显示更新。不过,初始数据的加载方式相同:将父级的状态传递到子级的属性中。我有几个 console.log()
s 似乎证明了为什么这应该有效,但是,它没有。那我做错了什么?
谢谢,希望一个问题不会太多!
我尝试克隆您的存储库,但它似乎嵌套在另一个存储库中。使用您当前的设置,这可能有效:
在您的 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>
);
然后,你的数据存储应该接收数据作为 prop,所以你可以像这样使用它:
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)
}
首先是您当前的代码,在 post 的末尾,我添加了一个替代解决方案,所以如果这是 tl;dr;直接跳到最后的片段:)
首先要注意的是您希望传递给 DataStore
的 data
变量,nl(我省略了一些部分,因为它们与讨论无关)
let data = [];
fetch("data/data.json").then(( response ) => {
data = JSON.parse( response.text() );
});
//... later down the code
var store = new DataStore(data);
在这里,您将在 fetch 调用的 then
承诺链中重新分配 data
变量。虽然赋值似乎有效,但现在 store.data
上的数据将是一个空数组,全局变量 data
现在将包含已解析的 response.text()
。您可能应该只推送刚刚解析的数据(但在我的示例中,我什至没有包括 DataStore
所以这仅供将来参考)
在您的 CodePen 中,您似乎混合了 Display
组件的道具和状态。这本质上是一个空操作,除非你真的知道自己在做什么,否则你不应该混合使用它们。另请注意,通过在 componentWillReceiveProps
生命周期方法中调用 this.setState
,应用程序将自动重新呈现超出需要的部分。我指的是这段代码:
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
渲染值,因为你从 App
.
备选方案
如评论中所述,您的代码目前在父子组件之间传递状态时所做的工作比它应该做的要多得多。
您应该记住的一件事是,当组件状态发生变化时,React 将自动重新渲染组件。当它发现虚拟 DOM 与真实 DOM 有差异时,它会自动替换那些组件。
从这个意义上说,您的 DataStore
是没有必要的。根据您希望如何管理状态,组件将对这些更改做出反应。
由于您的应用程序使用组件状态(这适用于小型应用程序,一旦您想迁移到更大的应用程序,您可能会想迁移到 Redux 或 MobX 之类的东西),您唯一需要做的就是要做的是确保设置正确的组件状态以触发渲染。
例如,我以更简洁的方式重写了您的代码:
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; }
<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>
现在,此示例代码中的某些内容可能需要一些解释。其一,此代码有 2 种组件类型,一种称为 Choice
的无状态表示组件,以及一种称为 App
的容器组件,它将其状态委托给它的子组件。
有关容器和展示组件的更多信息可以在 Dan Abramov(redux 创建者)blog
上找到上述概念的本质就是这样,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
,状态更改,并且应用程序重新呈现,允许 Choice
组件更新它的元素。
const Choice = ({ header, values, onChange, activeValue })
根据传递给它的道具,Choice
组件可以决定渲染时哪个是活动项目。如您所见,道具已被破坏。 header
、values
、onChange
和 activeValue
都是组件 props
上的属性,但是为了节省时间,我们可以将这些值赋值给一个变量并在渲染中使用它们。