fix(i18n): update Chinese translation of react and redux (#38786)
Co-authored-by: Zhicheng Chen <chenzhicheng@dayuwuxian.com>
This commit is contained in:
@ -3,26 +3,33 @@ id: 5a24c314108439a4d4036147
|
||||
title: Connect Redux to React
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: 将Redux连接到React
|
||||
forumTopicId: 301426
|
||||
localeTitle: 连接 Redux 和 React
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description">现在您已经编写了<code>mapStateToProps()</code>和<code>mapDispatchToProps()</code>函数,您可以使用它们将<code>state</code>和<code>dispatch</code>映射到您的某个React组件的<code>props</code> 。 React Redux的<code>connect</code>方法可以处理此任务。此方法采用两个可选参数: <code>mapStateToProps()</code>和<code>mapDispatchToProps()</code> 。它们是可选的,因为您可能拥有只需要访问<code>state</code>但不需要分派任何操作的组件,反之亦然。要使用此方法,请将函数作为参数传递,并立即使用组件调用结果。这种语法有点不寻常,看起来像: <code>connect(mapStateToProps, mapDispatchToProps)(MyComponent)</code> <strong>注意:</strong>如果要省略<code>connect</code>方法的其中一个参数,则在其位置传递<code>null</code> 。 </section>
|
||||
<section id='description'>
|
||||
既然写了<code>mapStateToProps()</code>、<code>mapDispatchToProps()</code>两个函数,现在你可以用它们来把<code>state</code>和<code>dispatch</code>映射到 React 组件的<code>props</code>了。React Redux 的<code>connect</code>方法可以完成这个任务。此方法有<code>mapStateToProps()</code>、<code>mapDispatchToProps()</code>两个可选参数,它们是可选的,原因是你的组件可能仅需要访问<code>状态</code>但不需要分发任何 actions,反之亦然。
|
||||
为了使用此方法,需要传入函数参数并在调用时传入组件。这种语法有些不寻常,如下所示:
|
||||
<code>connect(mapStateToProps, mapDispatchToProps)(MyComponent)</code>
|
||||
<strong>注意:</strong> 如果要省略<code>connect</code>方法中的某个参数,则应当用<code>null</code>替换这个参数。
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">代码编辑器具有<code>mapStateToProps()</code>和<code>mapDispatchToProps()</code>函数以及一个名为<code>Presentational</code>的新React组件。使用<code>ReactRedux</code>全局对象中的<code>connect</code>方法将此组件连接到Redux,并立即在<code>Presentational</code>组件上调用它。将结果分配给名为<code>ConnectedComponent</code>的新<code>const</code> ,该<code>const</code>表示连接的组件。就是这样,现在你已经连接到Redux了!尝试将<code>connect</code>的参数更改为<code>null</code>并观察测试结果。 </section>
|
||||
<section id='instructions'>
|
||||
在编辑器上有两个函数:<code>mapStateToProps()</code>、<code>mapDispatchToProps()</code>,还有一个叫<code>Presentational</code>的 React 组件。用<code>ReactRedux</code>全局对象中的<code>connect</code>方法将此组件连接到 Redux,并立即在<code>Presentational</code>组件中调用,把结果赋值给一个名为<code>ConnectedComponent</code>的代表已连接组件的新常量。大功告成!你已成功把 React 连接到 Redux!尝试更改任何一个<code>connect</code>参数为<code>null</code>并观察测试结果。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: <code>Presentational</code>组件应该呈现。
|
||||
- text: 应渲染<code>Presentational</code>组件。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })());
|
||||
- text: <code>Presentational</code>组件应通过<code>connect</code>接收prop <code>messages</code> 。
|
||||
- text: <code>Presentational</code>组件应通过<code>connect</code>接收一个<code>messages</code>属性。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const props = mockedComponent.find('Presentational').props(); return props.messages === '__INITIAL__STATE__'; })());
|
||||
- text: <code>Presentational</code>组件应通过<code>connect</code>接收prop <code>submitNewMessage</code> 。
|
||||
- text: <code>Presentational</code>组件应通过<code>connect</code>接收一个<code>submitNewMessage</code>属性。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const props = mockedComponent.find('Presentational').props(); return typeof props.submitNewMessage === 'function'; })());
|
||||
|
||||
```
|
||||
@ -66,7 +73,7 @@ class Presentational extends React.Component {
|
||||
};
|
||||
|
||||
const connect = ReactRedux.connect;
|
||||
// change code below this line
|
||||
// 请在本行以下添加你的代码
|
||||
|
||||
```
|
||||
|
||||
@ -77,7 +84,20 @@ const connect = ReactRedux.connect;
|
||||
<div id='jsx-teardown'>
|
||||
|
||||
```js
|
||||
console.info('after the test');
|
||||
|
||||
const store = Redux.createStore(
|
||||
(state = '__INITIAL__STATE__', action) => state
|
||||
);
|
||||
class AppWrapper extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ReactRedux.Provider store = {store}>
|
||||
<ConnectedComponent/>
|
||||
</ReactRedux.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
</div>
|
||||
@ -87,8 +107,43 @@ console.info('after the test');
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// solution required
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
messages: state
|
||||
}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (message) => {
|
||||
dispatch(addMessage(message));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
return <h3>This is a Presentational Component</h3>
|
||||
}
|
||||
};
|
||||
|
||||
const connect = ReactRedux.connect;
|
||||
// change code below this line
|
||||
|
||||
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
|
||||
```
|
||||
|
||||
/section>
|
||||
</section>
|
||||
|
@ -3,30 +3,35 @@ id: 5a24c314108439a4d4036148
|
||||
title: Connect Redux to the Messages App
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: 将Redux连接到消息应用程序
|
||||
forumTopicId: 301427
|
||||
localeTitle: 将 Redux 连接到 Messages App
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description">现在您已了解如何使用<code>connect</code>将React连接到Redux,您可以将您学到的知识应用于处理消息的React组件。在上一课中,您连接到Redux的组件名为<code>Presentational</code> ,这不是任意的。该术语<i>通常</i>是指未直接连接到Redux的React组件。他们只负责UI的呈现,并根据他们收到的道具来执行此操作。相比之下,容器组件连接到Redux。这些通常负责将操作分派给商店,并且经常将商店状态作为道具传递给子组件。 </section>
|
||||
<section id='description'>
|
||||
知道<code>connect</code>怎么实现 React 和 Redux 的连接后,我们可以在 React 组件中应用上面学到的内容。
|
||||
在上一课,连接到 Redux 的组件命名为<code>Presentational</code>,这个命名不是任意的,这样的术语通常是指未直接连接到 Redux 的 React 组件,他们只负责执行接收 props 的函数来实现 UI 的呈现。与上一挑战相比,本挑战需要把容器组件连接到 Redux。这些组件通常负责把 actions 分派给 store,且经常给子组件传入 store state 属性。
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">到目前为止,代码编辑器包含了您在本节中编写的所有代码。唯一的变化是React组件被重命名为<code>Presentational</code> 。创建一个名为<code>Container</code>的常量中保存的新组件,该常量使用<code>connect</code>将<code>Presentational</code>组件连接到Redux。然后,在<code>AppWrapper</code> ,渲染React Redux <code>Provider</code>组件。将Redux <code>store</code> <code>Provider</code>作为道具传递,并将<code>Container</code>作为子项呈现。设置完所有内容后,您将再次看到应用程序呈现给页面的消息。 </section>
|
||||
<section id='instructions'>
|
||||
到目前为止,我们的编辑器上已包含了整个章节的代码,唯一不同的是,React 组件被重新命名为<code>Presentational</code>,即展示层组件。创建一个新组件,保存在名为<code>Container</code>的常量中。这个常量用<code>connect</code>把<code>Presentational</code>组件和 Redux 连接起来。然后,在<code>AppWrapper</code>中渲染 React Redux 的<code>Provider</code>组件,给<code>Provider</code>传入 Redux<code>store</code>属性并渲染<code>Container</code>为子组件。完成这些,消息 app 应用会再次渲染页面。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: <code>AppWrapper</code>应该呈现给页面。
|
||||
- text: <code>AppWrapper</code>应渲染该页面。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })());
|
||||
- text: <code>Presentational</code>组件应呈现<code>h2</code> , <code>input</code> , <code>button</code>和<code>ul</code>元素。
|
||||
- text: <code>Presentational</code>组件应渲染<code>h2</code>、<code>input</code>、<code>button</code>、<code>ul</code>四个元素。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })());
|
||||
- text: <code>Presentational</code>组件应呈现<code>h2</code> , <code>input</code> , <code>button</code>和<code>ul</code>元素。
|
||||
- text: <code>Presentational</code>组件应渲染<code>h2</code>、<code>input</code>、<code>button</code>、<code>ul</code>四个元素。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); return ( PresentationalComponent.find('div').length === 1 && PresentationalComponent.find('h2').length === 1 && PresentationalComponent.find('button').length === 1 && PresentationalComponent.find('ul').length === 1 ); })());
|
||||
- text: <code>Presentational</code>组件应该从Redux商店接收<code>messages</code>作为道具。
|
||||
- text: <code>Presentational</code>组件应接收 Redux store 的<code>消息</code>属性。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })());
|
||||
- text: <code>Presentational</code>组件应该接收<code>submitMessage</code>操作创建者作为prop。
|
||||
- text: <code>Presentational</code>组件应接收创建 action 的函数<code>submitMessage</code>属性。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })());
|
||||
|
||||
```
|
||||
@ -68,7 +73,7 @@ class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ",
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
@ -82,7 +87,124 @@ class Presentational extends React.Component {
|
||||
submitMessage() {
|
||||
const currentMessage = this.state.input;
|
||||
this.setState({
|
||||
input: ",
|
||||
input: '',
|
||||
messages: this.state.messages.concat(currentMessage)
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// React-Redux:
|
||||
const mapStateToProps = (state) => {
|
||||
return { messages: state }
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (newMessage) => {
|
||||
dispatch(addMessage(newMessage))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
// 在此定义 Container 组件:
|
||||
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
// 完成返回声明:
|
||||
return (null);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
### After Test
|
||||
<div id='jsx-teardown'>
|
||||
|
||||
```js
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
const currentMessage = this.state.input;
|
||||
this.setState({
|
||||
input: '',
|
||||
messages: this.state.messages.concat(currentMessage)
|
||||
});
|
||||
}
|
||||
@ -124,7 +246,7 @@ const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
// define the Container component here:
|
||||
|
||||
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
constructor(props) {
|
||||
@ -132,31 +254,12 @@ class AppWrapper extends React.Component {
|
||||
}
|
||||
render() {
|
||||
// complete the return statement:
|
||||
return (null);
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Container/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
### After Test
|
||||
<div id='jsx-teardown'>
|
||||
|
||||
```js
|
||||
console.info('after the test');
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
```js
|
||||
// solution required
|
||||
```
|
||||
|
||||
/section>
|
||||
|
@ -3,38 +3,43 @@ id: 5a24c314108439a4d4036149
|
||||
title: Extract Local State into Redux
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: 将本地状态提取到Redux
|
||||
forumTopicId: 301428
|
||||
localeTitle: 将局部状态提取到 Redux 中
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description">你几乎完成!回想一下,您编写了所有Redux代码,以便Redux可以控制React消息应用程序的状态管理。现在Redux已连接,您需要从<code>Presentational</code>组件中提取状态管理并进入Redux。目前,您已连接Redux,但您正在<code>Presentational</code>组件中本地处理状态。 </section>
|
||||
<section id='description'>
|
||||
胜利就在眼前了!请回顾一下为管理 React messages app 的状态写的 Redux 代码。现在有了连接好的 Redux,你还要从<code>Presentational</code>组件中提取状态管理到 Redux,在<code>Presentational</code>组件内处理本地状态。
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">在<code>Presentational</code>组件中,首先,删除本地<code>state</code>中的<code>messages</code>属性。这些消息将由Redux管理。接下来,修改<code>submitMessage()</code>方法,以便从<code>this.props</code>调度<code>submitNewMessage()</code> ,并将当前消息输入作为参数传入本地<code>state</code> 。因为你删除<code>messages</code>从本地状态,删除<code>messages</code>从调用属性<code>this.setState()</code>在这里。最后,修改<code>render()</code>方法,使其映射到从<code>props</code>而不是<code>state</code>接收的消息。一旦做出这些更改,除了Redux管理状态之外,应用程序将继续运行相同的功能。这个例子也说明组件可以如何具有本地<code>state</code> :你的组件还是本地跟踪用户输入自己的<code>state</code> 。您可以看到Redux如何在React之上提供有用的状态管理框架。您最初仅使用React的本地状态获得了相同的结果,这通常可以通过简单的应用程序实现。但是,随着您的应用程序变得越来越大,越来越复杂,您的状态管理也是如此,这就是Redux解决的问题。 </section>
|
||||
<section id='instructions'>
|
||||
在<code>Presentational</code>组件中,先删除本地<code>state</code>中的<code>messages</code>属性,被删的 messages 将由 Redux 管理。接着,修改<code>submitMessage()</code>方法,使该方法从<code>this.props</code>那里分发<code>submitNewMessage()</code>;从本地<code>state</code>中传入当前消息输入作为参数。因本地状态删除了<code>messages</code>属性,所以在调用<code>this.setState()</code>时也要删除该属性。最后,修改<code>render()</code>方法,使其所映射的消息是从<code>props</code>接收的,而不是<code>state</code>
|
||||
完成这些更改后,我们的应用会实现 Redux 管理应用的状态,但它继续运行着相同的功能。此示例还阐明了组件获得本地状态的方式,即在自己的状态中继续跟踪用户本地输入。由此可见,Redux 为 React 提供了很有用的状态管理框架。先前,你仅使用 React 的本地状态也实现了相同的结果,这在应付简单的应用时通常是可行的。但是,随着应用变得越来越大,越来越复杂,应用的状态管理也变得非常困难,Redux 就是为解决这样的问题而诞生的。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: <code>AppWrapper</code>应该呈现给页面。
|
||||
- text: <code>AppWrapper</code>应该渲染该到页面。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })());
|
||||
- text: <code>Presentational</code>组件应呈现<code>h2</code> , <code>input</code> , <code>button</code>和<code>ul</code>元素。
|
||||
- text: <code>Presentational</code> 应该渲染到页面上.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })());
|
||||
- text: <code>Presentational</code>组件应呈现<code>h2</code> , <code>input</code> , <code>button</code>和<code>ul</code>元素。
|
||||
- text: <code>Presentational</code>组件应渲染<code>h2</code>、<code>input</code>、<code>button</code>、<code>ul</code>四个元素。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); return ( PresentationalComponent.find('div').length === 1 && PresentationalComponent.find('h2').length === 1 && PresentationalComponent.find('button').length === 1 && PresentationalComponent.find('ul').length === 1 ); })());
|
||||
- text: <code>Presentational</code>组件应该从Redux商店接收<code>messages</code>作为道具。
|
||||
- text: <code>Presentational</code>组件应接收 Redux store 的<code>消息</code>属性。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return Array.isArray(props.messages); })());
|
||||
- text: <code>Presentational</code>组件应该接收<code>submitMessage</code>操作创建者作为prop。
|
||||
- text: <code>Presentational</code>组件应接收创建 action 的函数<code>submitMessage</code>属性。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalComponent = mockedComponent.find('Presentational'); const props = PresentationalComponent.props(); return typeof props.submitNewMessage === 'function'; })());
|
||||
- text: <code>Presentational</code>组件的状态应包含一个属性<code>input</code> ,该属性初始化为空字符串。
|
||||
- text: <code>Presentational</code>组件的状态应包含一个初始化为空字符串的input属性。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const PresentationalState = mockedComponent.find('Presentational').instance().state; return typeof PresentationalState.input === 'string' && Object.keys(PresentationalState).length === 1; })());
|
||||
- text: 输入<code>input</code>元素应该更新<code>Presentational</code>组件的状态。
|
||||
- text: 键入<code>input</code>元素应更新<code>Presentational</code>组件的状态。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const testValue = ''__MOCK__INPUT__''; const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find(''input'').simulate(''change'', { target: { value: v }}); let initialInput = mockedComponent.find(''Presentational'').find(''input''); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); const updatedInput = updated.find(''Presentational'').find(''input''); assert(initialInput.props().value === '''' && updatedInput.props().value === ''__MOCK__INPUT__''); }; '
|
||||
- text: 在<code>Presentational</code>组件上调度<code>submitMessage</code>应更新Redux存储并清除本地状态的输入。
|
||||
- text: 在<code>Presentational</code>组件上 dispatch <code>submitMessage</code>应更新 Redux store 并清除本地状态中的输入。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); let beforeProps = mockedComponent.find(''Presentational'').props(); const testValue = ''__TEST__EVENT__INPUT__''; const causeChange = (c, v) => c.find(''input'').simulate(''change'', { target: { value: v }}); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const clickButton = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent )}; const afterChange = await changed(); const afterChangeInput = afterChange.find(''input'').props().value; const afterClick = await clickButton(); const afterProps = mockedComponent.find(''Presentational'').props(); assert(beforeProps.messages.length === 0 && afterChangeInput === testValue && afterProps.messages.pop() === testValue && afterClick.find(''input'').props().value === ''''); }; '
|
||||
- text: <code>Presentational</code>组件应该呈现来自Redux存储的<code>messages</code> 。
|
||||
- text: <code>Presentational</code>组件应渲染 Redux store 中的<code>messages</code>
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); let beforeProps = mockedComponent.find(''Presentational'').props(); const testValue = ''__TEST__EVENT__INPUT__''; const causeChange = (c, v) => c.find(''input'').simulate(''change'', { target: { value: v }}); const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const clickButton = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent )}; const afterChange = await changed(); const afterChangeInput = afterChange.find(''input'').props().value; const afterClick = await clickButton(); const afterProps = mockedComponent.find(''Presentational'').props(); assert(beforeProps.messages.length === 0 && afterChangeInput === testValue && afterProps.messages.pop() === testValue && afterClick.find(''input'').props().value === '''' && afterClick.find(''ul'').childAt(0).text() === testValue); }; '
|
||||
|
||||
```
|
||||
@ -75,12 +80,12 @@ const store = Redux.createStore(messageReducer);
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
// Change code below this line
|
||||
// 请在本行以下添加你的代码
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ",
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
@ -93,7 +98,7 @@ class Presentational extends React.Component {
|
||||
}
|
||||
submitMessage() {
|
||||
this.setState({
|
||||
input: ",
|
||||
input: '',
|
||||
messages: this.state.messages.concat(this.state.input)
|
||||
});
|
||||
}
|
||||
@ -117,6 +122,121 @@ class Presentational extends React.Component {
|
||||
);
|
||||
}
|
||||
};
|
||||
// 请在本行以上添加你的代码
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {messages: state}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: (message) => {
|
||||
dispatch(addMessage(message))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational);
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Container/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
### After Test
|
||||
<div id='jsx-teardown'>
|
||||
|
||||
```js
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// Redux:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React:
|
||||
const Provider = ReactRedux.Provider;
|
||||
const connect = ReactRedux.connect;
|
||||
|
||||
// Change code below this line
|
||||
class Presentational extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ''
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
this.props.submitNewMessage(this.state.input);
|
||||
this.setState({
|
||||
input: ''
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.props.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
// Change code above this line
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
@ -142,28 +262,6 @@ class AppWrapper extends React.Component {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
### After Test
|
||||
<div id='jsx-teardown'>
|
||||
|
||||
```js
|
||||
console.info('after the test');
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
```js
|
||||
// solution required
|
||||
```
|
||||
|
||||
/section>
|
||||
|
@ -3,32 +3,37 @@ id: 5a24c314108439a4d4036143
|
||||
title: Extract State Logic to Redux
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: 将状态逻辑提取到Redux
|
||||
forumTopicId: 301429
|
||||
localeTitle: 提取状态逻辑给 Redux
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description">现在您已完成React组件,您需要将其在其<code>state</code>本地执行的逻辑移动到Redux中。这是将简单的React应用程序连接到Redux的第一步。您的应用程序的唯一功能是将用户的新消息添加到无序列表中。该示例很简单,以演示React和Redux如何协同工作。 </section>
|
||||
<section id='description'>
|
||||
完成 React 组件后,我们需要把在本地<code>状态</code>执行的逻辑移到 Redux 中,这是为小规模 React 应用添加 Redux 的第一步。该应用的唯一功能是把用户的新消息添加到无序列表中。下面我们用简单的示例来演示 React 和 Redux 之间的配合。
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">首先,定义一个动作类型'ADD'并将其设置为const <code>ADD</code> 。接下来,定义一个动作创建器<code>addMessage()</code> ,它创建添加消息的动作。您需要将<code>message</code>给此操作创建者,并在返回的<code>action</code>包含该消息。然后创建一个名为<code>messageReducer()</code>的reducer来处理消息的状态。初始状态应该等于空数组。此reducer应向状态中保存的消息数组添加消息,或返回当前状态。最后,创建Redux存储并将其传递给reducer。 </section>
|
||||
<section id='instructions'>
|
||||
首先,定义 action 的类型 'ADD',将其设置为常量<code>ADD</code>。接着,定义创建 action 的函数<code>addMessage()</code>,用该函数创建添加消息的 action,把<code>message</code>传给创建 action 的函数并返回包含该消息的<code>action</code>
|
||||
接着,创建名为<code>messageReducer()</code>的 reducer 方法,为这些消息处理状态。初始状态应为空数组。reducer 向状态中的消息数组添加消息,或返回当前状态。最后,创建 Redux store 并传给 reducer。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: const <code>ADD</code>应该存在并保持一个等于字符串<code>ADD</code>的值
|
||||
- text: 应存在一个值为字符串<code>ADD</code>的常量<code>ADD</code>。
|
||||
testString: assert(ADD === 'ADD');
|
||||
- text: 动作创建者<code>addMessage</code>应返回<code>type</code>等于<code>ADD</code>的对象,并且消息等于传入的消息。
|
||||
- text: 创建 action 的函数<code>addMessage</code>应返回<code>type</code>等于<code>ADD</code>的对象,其返回的消息即被传入的消息。
|
||||
testString: assert((function() { const addAction = addMessage('__TEST__MESSAGE__'); return addAction.type === ADD && addAction.message === '__TEST__MESSAGE__'; })());
|
||||
- text: <code>messageReducer</code>应该是一个函数。
|
||||
- text: <code>messageReducer</code>应是一个函数。
|
||||
testString: assert(typeof messageReducer === 'function');
|
||||
- text: 存储应该存在并且初始状态设置为空数组。
|
||||
- text: 存在一个 store 且其初始状态为空数组。
|
||||
testString: assert((function() { const initialState = store.getState(); return typeof store === 'object' && initialState.length === 0; })());
|
||||
- text: 对商店调度<code>addMessage</code>应该<code>addMessage</code>向状态中保存的消息数组添加新消息。
|
||||
- text: 分发<code>addMessage</code>到 store 应添加新消息到状态中消息数组。
|
||||
testString: assert((function() { const initialState = store.getState(); const isFrozen = DeepFreeze(initialState); store.dispatch(addMessage('__A__TEST__MESSAGE')); const addState = store.getState(); return (isFrozen && addState[0] === '__A__TEST__MESSAGE'); })());
|
||||
- text: 如果使用任何其他操作调用, <code>messageReducer</code>应返回当前状态。
|
||||
- text: <code>messageReducer</code>被其它任何 actions 调用时应返回当前状态。
|
||||
testString: 'assert((function() { const addState = store.getState(); store.dispatch({type: ''FAKE_ACTION''}); const testState = store.getState(); return (addState === testState); })());'
|
||||
|
||||
```
|
||||
@ -41,7 +46,7 @@ tests:
|
||||
<div id='jsx-seed'>
|
||||
|
||||
```jsx
|
||||
// define ADD, addMessage(), messageReducer(), and store here:
|
||||
// 请在此处定义 ADD、addMessage()、messageReducer()、store:
|
||||
|
||||
```
|
||||
|
||||
@ -54,8 +59,31 @@ tests:
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// solution required
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
```
|
||||
|
||||
/section>
|
||||
</section>
|
||||
|
||||
|
@ -3,26 +3,32 @@ id: 5a24c314108439a4d4036141
|
||||
title: Getting Started with React Redux
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: React Redux入门
|
||||
forumTopicId: 301430
|
||||
localeTitle: React 和 Redux 入门
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description">这一系列挑战介绍了如何将Redux与React一起使用。首先,这里回顾一下每种技术的一些关键原则。 React是一个您提供数据的视图库,然后以高效,可预测的方式呈现视图。 Redux是一个状态管理框架,可用于简化应用程序状态的管理。通常,在React Redux应用程序中,您可以创建一个Redux存储库来管理整个应用程序的状态。您的React组件仅订阅商店中与其角色相关的数据。然后,直接从React组件调度操作,然后触发存储更新。虽然React组件可以在本地管理自己的状态,但是当您拥有复杂的应用程序时,通常最好将应用程序状态保存在Redux的单个位置。当单个组件可能仅具有特定于其的本地状态时,存在例外情况。最后,因为Redux不是设计用于开箱即用的React,所以你需要使用<code>react-redux</code>包。它为您提供了Redux的传递方式<code>state</code> ,并<code>dispatch</code>到你的反应的组分作为<code>props</code> 。在接下来的几个挑战中,首先,您将创建一个简单的React组件,允许您输入新的文本消息。这些将添加到视图中显示的数组中。这应该是您在React课程中学到的内容的一个很好的回顾。接下来,您将创建一个Redux存储和操作来管理messages数组的状态。最后,您将使用<code>react-redux</code>将Redux存储与您的组件连接,从而将本地状态提取到Redux存储中。 </section>
|
||||
<section id='description'>
|
||||
这一系列挑战介绍的是 Redux 和 React 的配合,我们先来回顾一下这两种技术的关键原则是什么。React 是提供数据的视图库,能以高效、可预测的方式渲染视图。Redux 是状态管理框架,可用于简化 APP 应用状态的管理。在 React Redux app 应用中,通常可创建单一的 Redux store 来管理整个应用的状态。React 组件仅订阅 store 中与其角色相关的数据,你可直接从 React 组件中分发 actions 以触发 store 对象的更新。
|
||||
React 组件可以在本地管理自己的状态,但是对于复杂的应用来说,它的状态最好是用 Redux 保存在单一位置,有特定本地状态的独立组件例外。最后一点是,Redux 没有内置的 React,需要安装<code>react-redux</code>包,通过这个方式把 Redux 的<code>state</code>和<code>dispatch</code>作为<code>props</code>传给组件。
|
||||
在接下来的挑战中,先要创建一个可输入新文本消息的 React 组件,添加这些消息到数组里,在视图上显示数组。接着,创建 Redux store 和 actions 来管理消息数组的状态。最后,使用<code>react-redux</code>连接 Redux store 和组件,从而将本地状态提取到 Redux store 中。
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">从<code>DisplayMessages</code>组件开始。一个构造添加到该组件,并使用具有两个属性的状态初始化: <code>input</code> ,其被设置为一个空字符串,和<code>messages</code> ,这是设置为空数组。 </section>
|
||||
<section id='instructions'>
|
||||
创建<code>DisplayMessages</code>组件,把构造函数添加到此组件中,使用含两个属性的状态初始化该组件,这两个属性为:input(设置为空字符串),<code>messages</code>(设置为空数组)。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: <code>DisplayMessages</code>组件应呈现一个空的<code>div</code>元素。
|
||||
- text: <code>DisplayMessages</code>组件应渲染空的<code>div</code>元素。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })());
|
||||
- text: 应该使用<code>super</code>正确调用<code>DisplayMessages</code>构造函数,传入<code>props</code> 。
|
||||
- text: <code>DisplayMessages</code>组件的构造函数应调用<code>super</code>,传入<code>props</code>。
|
||||
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })());
|
||||
- text: '<code>DisplayMessages</code>组件的初始状态应等于<code>{input: "", messages: []}</code> 。'
|
||||
- text: '<code>DisplayMessages</code>组件的初始状态应是<code>{input: "", messages: []}</code>。'
|
||||
testString: "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return typeof initialState === 'object' && initialState.input === '' && Array.isArray(initialState.messages) && initialState.messages.length === 0; })());"
|
||||
|
||||
```
|
||||
@ -36,14 +42,13 @@ tests:
|
||||
|
||||
```jsx
|
||||
class DisplayMessages extends React.Component {
|
||||
// change code below this line
|
||||
// 请在本行以下添加你的代码
|
||||
|
||||
// change code above this line
|
||||
// 请在本行以上添加你的代码
|
||||
render() {
|
||||
return <div />
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</div>
|
||||
@ -53,7 +58,7 @@ class DisplayMessages extends React.Component {
|
||||
<div id='jsx-teardown'>
|
||||
|
||||
```js
|
||||
console.info('after the test');
|
||||
ReactDOM.render(<DisplayMessages />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
</div>
|
||||
@ -63,8 +68,21 @@ console.info('after the test');
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// solution required
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return <div/>
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
/section>
|
||||
</section>
|
||||
|
||||
|
@ -3,33 +3,41 @@ id: 5a24c314108439a4d4036142
|
||||
title: Manage State Locally First
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: 在当地管理国家
|
||||
forumTopicId: 301431
|
||||
localeTitle: 首先在本地管理状态
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description">在这里,您将完成创建<code>DisplayMessages</code>组件。 </section>
|
||||
<section id='description'>
|
||||
这一关的任务是完成<code>DisplayMessages</code>组件的创建。
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">首先,在<code>render()</code>方法中,让组件呈现<code>input</code>元素, <code>button</code>元素和<code>ul</code>元素。当<code>input</code>元素改变时,它应该触发<code>handleChange()</code>方法。此外, <code>input</code>元素应该呈现处于组件状态的<code>input</code>值。 <code>button</code>元素应在单击时触发<code>submitMessage()</code>方法。其次,写下这两种方法。该<code>handleChange()</code>方法应该更新<code>input</code>与用户正在打字。 <code>submitMessage()</code>方法应将当前消息(存储在<code>input</code> )连接到本地状态的<code>messages</code>数组,并清除<code>input</code>的值。最后,使用<code>ul</code>映射<code>messages</code>数组并将其作为<code>li</code>元素列表呈现给屏幕。 </section>
|
||||
<section id='instructions'>
|
||||
首先,在<code>render()</code>方法中,让组件渲染<code>input</code>、<code>button</code>、<code>ul</code>三个元素。<code>input</code>元素的改变会触发<code>handleChange()</code>方法。此外,<code>input</code>元素会渲染组件状态中<code>input</code>的值。点击按钮<code>button</code>需触发<code>submitMessage()</code>方法。
|
||||
接着,写出这两种方法。<code>handleChange()</code>方法会更新<code>input</code>为用户正在输入的内容。<code>submitMessage()</code>方法把当前存储在<code>input</code>的消息与本地状态的<code>messages</code>数组连接起来,并清除<code>input</code>的值。
|
||||
最后,在<code>ul</code>中展示<code>messages</code>数组,其中每个元素内容需放到<code>li</code>元素内。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: '<code>DisplayMessages</code>组件应使用等于<code>{ input: "", messages: [] }</code>的状态进行初始化。'
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return ( typeof initialState === "object" && initialState.input === "" && initialState.messages.length === 0); })());
|
||||
- text: <code>DisplayMessages</code>组件应该呈现包含<code>h2</code>元素, <code>button</code>元素, <code>ul</code>元素和<code>li</code>元素作为子元素的<code>div</code> 。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const state = () => { mockedComponent.setState({messages: ["__TEST__MESSAGE"]}); return waitForIt(() => mockedComponent )}; const updated = await state(); assert(updated.find("div").length === 1 && updated.find("h2").length === 1 && updated.find("button").length === 1 && updated.find("ul").length === 1, "The <code>DisplayMessages</code> component should render a <code>div</code> containing an <code>h2</code> element, a <code>button</code> element, a <code>ul</code> element, and <code>li</code> elements as children."); }; '
|
||||
- text: <code>input</code>元素应该以本地状态呈现<code>input</code>值。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find("input").simulate("change", { target: { value: v }}); const testValue = "__TEST__EVENT__INPUT"; const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); assert(updated.find("input").props().value === testValue, "The <code>input</code> element should render the value of <code>input</code> in local state."); }; '
|
||||
- text: 调用方法<code>handleChange</code>应该将状态中的<code>input</code>值更新为当前输入。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find("input").simulate("change", { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage = "__TEST__EVENT__MESSAGE__"; const changed = () => { causeChange(mockedComponent, testMessage); return waitForIt(() => mockedComponent )}; const afterInput = await changed(); assert(initialState.input === "" && afterInput.state().input === "__TEST__EVENT__MESSAGE__", "Calling the method <code>handleChange</code> should update the <code>input</code> value in state to the current input."); }; '
|
||||
- text: 单击“ <code>Add message</code>按钮应调用方法<code>submitMessage</code> ,该方法<code>submitMessage</code>当前<code>input</code>添加到状态中的<code>messages</code>数组。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find("input").simulate("change", { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage_1 = "__FIRST__MESSAGE__"; const firstChange = () => { causeChange(mockedComponent, testMessage_1); return waitForIt(() => mockedComponent )}; const firstResult = await firstChange(); const firstSubmit = () => { mockedComponent.find("button").simulate("click"); return waitForIt(() => mockedComponent )}; const afterSubmit_1 = await firstSubmit(); const submitState_1 = afterSubmit_1.state(); const testMessage_2 = "__SECOND__MESSAGE__"; const secondChange = () => { causeChange(mockedComponent, testMessage_2); return waitForIt(() => mockedComponent )}; const secondResult = await secondChange(); const secondSubmit = () => { mockedComponent.find("button").simulate("click"); return waitForIt(() => mockedComponent )}; const afterSubmit_2 = await secondSubmit(); const submitState_2 = afterSubmit_2.state(); assert(initialState.messages.length === 0 && submitState_1.messages.length === 1 && submitState_2.messages.length === 2 && submitState_2.messages[1] === testMessage_2, "Clicking the <code>Add message</code> button should call the method <code>submitMessage</code> which should add the current <code>input</code> to the <code>messages</code> array in state."); }; '
|
||||
- text: <code>submitMessage</code>方法应该清除当前输入。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find("input").simulate("change", { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage = "__FIRST__MESSAGE__"; const firstChange = () => { causeChange(mockedComponent, testMessage); return waitForIt(() => mockedComponent )}; const firstResult = await firstChange(); const firstState = firstResult.state(); const firstSubmit = () => { mockedComponent.find("button").simulate("click"); return waitForIt(() => mockedComponent )}; const afterSubmit = await firstSubmit(); const submitState = afterSubmit.state(); assert(firstState.input === testMessage && submitState.input === "", "The <code>submitMessage</code> method should clear the current input."); }; '
|
||||
- text: '<code>DisplayMessages</code>组件的初始状态应是<code>{ input: "", messages: [] }</code>。'
|
||||
testString: 'assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return ( typeof initialState === ''object'' && initialState.input === '''' && initialState.messages.length === 0); })());'
|
||||
- text: <code>DisplayMessages</code>组件应渲染含<code>h2</code>、<code>button</code>、<code>ul</code>、<code>li</code>四个子元素的<code>div</code>。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const state = () => { mockedComponent.setState({messages: [''__TEST__MESSAGE'']}); return waitForIt(() => mockedComponent )}; const updated = await state(); assert(updated.find(''div'').length === 1 && updated.find(''h2'').length === 1 && updated.find(''button'').length === 1 && updated.find(''ul'').length === 1 && updated.find(''li'').length > 0); }; '
|
||||
- text: <code>input</code>元素应渲染本地状态中的<code>input</code>值。
|
||||
testString: assert(code.match(/this\.state\.messages\.map/g));
|
||||
- text: 调用<code>handleChange</code>方法时应更新状态中的<code>input</code>值为当前输入。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find(''input'').simulate(''change'', { target: { value: v }}); const testValue = ''__TEST__EVENT__INPUT''; const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); assert(updated.find(''input'').props().value === testValue); }; '
|
||||
- text: 单击<code>Add message</code>按钮应调用<code>submitMessage</code>方法,添加当前<code>输入</code>到状态中的<code>消息</code>数组。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find(''input'').simulate(''change'', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage = ''__TEST__EVENT__MESSAGE__''; const changed = () => { causeChange(mockedComponent, testMessage); return waitForIt(() => mockedComponent )}; const afterInput = await changed(); assert(initialState.input === '''' && afterInput.state().input === ''__TEST__EVENT__MESSAGE__''); }; '
|
||||
- text: <code>submitMessage</code>方法应清除当前输入。
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find(''input'').simulate(''change'', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage_1 = ''__FIRST__MESSAGE__''; const firstChange = () => { causeChange(mockedComponent, testMessage_1); return waitForIt(() => mockedComponent )}; const firstResult = await firstChange(); const firstSubmit = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent )}; const afterSubmit_1 = await firstSubmit(); const submitState_1 = afterSubmit_1.state(); const testMessage_2 = ''__SECOND__MESSAGE__''; const secondChange = () => { causeChange(mockedComponent, testMessage_2); return waitForIt(() => mockedComponent )}; const secondResult = await secondChange(); const secondSubmit = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent )}; const afterSubmit_2 = await secondSubmit(); const submitState_2 = afterSubmit_2.state(); assert(initialState.messages.length === 0 && submitState_1.messages.length === 1 && submitState_2.messages.length === 2 && submitState_2.messages[1] === testMessage_2); }; '
|
||||
- text: The <code>submitMessage</code> method should clear the current input.
|
||||
testString: 'async () => { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const waitForIt = (fn) => new Promise((resolve, reject) => setTimeout(() => resolve(fn()), 100)); const causeChange = (c, v) => c.find(''input'').simulate(''change'', { target: { value: v }}); const initialState = mockedComponent.state(); const testMessage = ''__FIRST__MESSAGE__''; const firstChange = () => { causeChange(mockedComponent, testMessage); return waitForIt(() => mockedComponent )}; const firstResult = await firstChange(); const firstState = firstResult.state(); const firstSubmit = () => { mockedComponent.find(''button'').simulate(''click''); return waitForIt(() => mockedComponent )}; const afterSubmit = await firstSubmit(); const submitState = afterSubmit.state(); assert(firstState.input === testMessage && submitState.input === ''''); }; '
|
||||
|
||||
```
|
||||
|
||||
@ -45,24 +53,23 @@ class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ",
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
}
|
||||
// add handleChange() and submitMessage() methods here
|
||||
// 请把 handleChange()、submitMessage() 写在这里
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
{ /* render an input, button, and ul here */ }
|
||||
<h2>键入新 Message</h2>
|
||||
{ /* 在此渲染 input、button、ul*/ }
|
||||
|
||||
{ /* change code above this line */ }
|
||||
{ /* 请在本行以上添加你的代码 */ }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</div>
|
||||
@ -72,7 +79,7 @@ class DisplayMessages extends React.Component {
|
||||
<div id='jsx-teardown'>
|
||||
|
||||
```js
|
||||
console.info('after the test');
|
||||
ReactDOM.render(<DisplayMessages />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
</div>
|
||||
@ -82,8 +89,50 @@ console.info('after the test');
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// solution required
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
const currentMessage = this.state.input;
|
||||
this.setState({
|
||||
input: '',
|
||||
messages: this.state.messages.concat(currentMessage)
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
/section>
|
||||
</section>
|
||||
|
@ -3,28 +3,42 @@ id: 5a24c314108439a4d4036146
|
||||
title: Map Dispatch to Props
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: 将调度映射到道具
|
||||
forumTopicId: 301432
|
||||
localeTitle: 映射 Dispatch 到 Props
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description"> <code>mapDispatchToProps()</code>函数用于为React组件提供特定的操作创建器,以便它们可以针对Redux存储分派操作。它的结构与您在上一次挑战中编写的<code>mapStateToProps()</code>函数类似。它返回一个对象,该对象将调度操作映射到属性名称,后者成为组件<code>props</code> 。但是,每个属性都返回一个函数,该函数使用动作创建者和任何相关的动作数据来调用<code>dispatch</code> ,而不是返回一个<code>state</code> 。您可以访问此<code>dispatch</code>因为它在您定义函数时作为参数传递给<code>mapDispatchToProps()</code> ,就像您将<code>state</code>传递给<code>mapStateToProps()</code> 。在幕后,阵营终极版使用终极版的<code>store.dispatch()</code>进行这些分派<code>mapDispatchToProps()</code>这类似于它将<code>store.subscribe()</code>用于映射到<code>state</code>组件。例如,您有一个<code>loginUser()</code>动作创建者,它将<code>username</code>作为动作有效负载。从<code>mapDispatchToProps()</code>为此动作创建者返回的对象看起来像: <blockquote> { <br> submitLoginUser:function(username){ <br>调度(loginUser(用户名)); <br> } <br> } </blockquote></section>
|
||||
<section id='description'>
|
||||
<code>mapDispatchToProps()</code>函数可为 React 组件提供特定的创建 action 的函数,以便组件可 dispatch actions,从而更改 Redux store 中的数据。该函数的结构跟上一挑战中的<code>mapStateToProps()</code>函数相似,它返回一个对象,把 dispatch actions 映射到属性名上,该属性名成为<code>props</code>。然而,每个属性都返回一个用 action creator 及与 action 相关的所有数据调用<code>dispatch</code>的函数,而不是返回<code>state</code>的一部分。你可以访问<code>dispatch</code>,因为在定义函数时,我们以参数形式把它传入<code>mapDispatchToProps()</code>了,这跟<code>state</code>传入<code>mapDispatchToProps()</code>是一样的。在幕后,React Redux 用 Redux 的<code>store.dispatch()</code>来管理这些含<code>mapDispatchToProps()</code>的dispatches,这跟它使用<code>store.subscribe()</code>来订阅映射到<code>state</code>的组件的方式类似。
|
||||
例如,创建 action 的函数<code>loginUser()</code>把<code>username</code>作为 action payload,<code>mapDispatchToProps()</code>返回给创建 action 的函数的对象如下:
|
||||
|
||||
```jsx
|
||||
{
|
||||
submitLoginUser: function(username) {
|
||||
dispatch(loginUser(username));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">代码编辑器提供了一个名为<code>addMessage()</code>的动作创建器。编写函数<code>mapDispatchToProps()</code> ,将<code>dispatch</code>作为参数,然后返回一个对象。该对象应该将一个属性<code>submitNewMessage</code>设置为dispatch函数,该函数在调度<code>addMessage()</code>时为新消息添加一个参数。 </section>
|
||||
<section id='instructions'>
|
||||
编辑器上提供的是创建 action 的函数<code>addMessage()</code>。写出接收<code>dispatch</code>为参数的函数<code>mapDispatchToProps()</code>,返回一个 dispatch 函数对象,其属性为<code>submitNewMessage</code>。该函数在 dispatch <code>addMessage()</code>时为新消息提供一个参数。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: <code>addMessage</code>应该返回一个带有键<code>type</code>和<code>message</code>的对象。
|
||||
- text: <code>addMessage</code>应返回含<code>type</code>和<code>message</code>两个键的对象。
|
||||
testString: assert((function() { const addMessageTest = addMessage(); return ( addMessageTest.hasOwnProperty('type') && addMessageTest.hasOwnProperty('message')); })());
|
||||
- text: <code>mapDispatchToProps</code>应该是一个函数。
|
||||
- text: <code>mapDispatchToProps</code>应为函数。
|
||||
testString: assert(typeof mapDispatchToProps === 'function');
|
||||
- text: <code>mapDispatchToProps</code>应该返回一个对象。
|
||||
- text: <code>mapDispatchToProps</code>应返回一个对象。
|
||||
testString: assert(typeof mapDispatchToProps() === 'object');
|
||||
- text: 使用<code>submitNewMessage</code>的<code>mapDispatchToProps</code>调度<code>addMessage</code>应该向调度函数返回一条消息。
|
||||
- text: 从<code>mapDispatchToProps</code>通过<code>submitNewMessage</code>分发<code>addMessage</code>,应向 dispatch 函数返回一条消息。
|
||||
testString: assert((function() { let testAction; const dispatch = (fn) => { testAction = fn; }; let dispatchFn = mapDispatchToProps(dispatch); dispatchFn.submitNewMessage('__TEST__MESSAGE__'); return (testAction.type === 'ADD' && testAction.message === '__TEST__MESSAGE__'); })());
|
||||
|
||||
```
|
||||
@ -44,7 +58,8 @@ const addMessage = (message) => {
|
||||
}
|
||||
};
|
||||
|
||||
// change code below this line
|
||||
// 请在本行以下添加你的代码
|
||||
|
||||
|
||||
```
|
||||
|
||||
@ -57,8 +72,24 @@ const addMessage = (message) => {
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// solution required
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: 'ADD',
|
||||
message: message
|
||||
}
|
||||
};
|
||||
|
||||
// change code below this line
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
submitNewMessage: function(message) {
|
||||
dispatch(addMessage(message));
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
/section>
|
||||
</section>
|
||||
|
@ -3,28 +3,34 @@ id: 5a24c314108439a4d4036145
|
||||
title: Map State to Props
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: 将状态映射到道具
|
||||
forumTopicId: 301433
|
||||
localeTitle: 映射 State 到 Props
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description"> <code>Provider</code>组件允许您为React组件提供<code>state</code>和<code>dispatch</code> ,但您必须准确指定所需的状态和操作。这样,您可以确保每个组件只能访问所需的状态。您可以通过创建两个函数来完成此任务: <code>mapStateToProps()</code>和<code>mapDispatchToProps()</code> 。在这些函数中,您可以声明要访问的状态段以及需要分配的操作创建者。一旦这些功能到位,您将看到如何使用React Redux <code>connect</code>方法在另一个挑战中将它们连接到您的组件。 <strong>注意:</strong>在幕后,React Redux使用<code>store.subscribe()</code>方法实现<code>mapStateToProps()</code> 。 </section>
|
||||
<section id='description'>
|
||||
<code>Provider</code>可向 React 组件提供<code>state</code>和<code>dispatch</code>,但你必须确切地指定所需要的 state 和 actions,以确保每个组件只能访问所需的 state。完成这个任务,你需要创建两个函数:<code>mapStateToProps()</code>、<code>mapDispatchToProps()</code>。
|
||||
在这两个函数中,声明 state 中函数所要访问的部分及需要 dispatch 的创建 action 的函数。完成这些,我们就可以迎接下一个挑战,学习如何使用 React Redux 的<code>connect</code>方法来把函数连接到组件了。
|
||||
<strong>注意:</strong> 在幕后,React Redux 用<code>store.subscribe()</code>方法来实现<code>mapStateToProps()</code>。
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">创建一个函数<code>mapStateToProps()</code> 。此函数应将<code>state</code>作为参数,然后返回将该状态映射到特定属性名称的对象。您可以通过<code>props</code>访问这些属性。由于此示例将应用程序的整个状态保存在单个数组中,因此您可以将整个状态传递给组件。在要返回的对象中创建属性<code>messages</code> ,并将其设置为<code>state</code> 。 </section>
|
||||
<section id='instructions'>
|
||||
创建<code>mapStateToProps()</code>函数,以<code>state</code>为参数,然后返回一个对象,该对象把 state 映射到特定属性名上,这些属性能通过<code>props</code>访问组件。由于此示例把 app 应用的整个状态保存在单一数组中,你可把整个状态传给组件。在返回的对象中创建<code>messages</code>属性,并设置为<code>state</code>。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: const <code>state</code>应该是一个空数组。
|
||||
- text: 常量<code>state</code>应为空数组。
|
||||
testString: assert(Array.isArray(state) && state.length === 0);
|
||||
- text: <code>mapStateToProps</code>应该是一个函数。
|
||||
- text: <code>mapStateToProps</code>应为函数。
|
||||
testString: assert(typeof mapStateToProps === 'function');
|
||||
- text: <code>mapStateToProps</code>应该返回一个对象。
|
||||
- text: <code>mapStateToProps</code>应返还一个对象。
|
||||
testString: assert(typeof mapStateToProps() === 'object');
|
||||
- text: 将数组作为状态传递给<code>mapStateToProps</code>应该返回分配给<code>messages</code>键的数组。
|
||||
- text: 把 state 数组传入<code>mapStateToProps</code>后应返回赋值给<code>messages</code>键的数组。
|
||||
testString: assert(mapStateToProps(['messages']).messages.pop() === 'messages');
|
||||
|
||||
```
|
||||
@ -39,7 +45,7 @@ tests:
|
||||
```jsx
|
||||
const state = [];
|
||||
|
||||
// change code below this line
|
||||
// 请在本行以下添加你的代码
|
||||
|
||||
```
|
||||
|
||||
@ -52,8 +58,17 @@ const state = [];
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// solution required
|
||||
const state = [];
|
||||
|
||||
// change code below this line
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
messages: state
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
/section>
|
||||
</section>
|
||||
|
@ -3,22 +3,29 @@ id: 5a24c314108439a4d403614a
|
||||
title: Moving Forward From Here
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
forumTopicId: 301434
|
||||
localeTitle: 从这里前进
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description">恭喜!你完成了React和Redux的课程。在继续之前,最后一项值得指出。通常,您不会在这样的代码编辑器中编写React应用程序。如果您在自己的计算机上使用npm和文件系统,这个挑战可以让您一瞥语法的样子。代码看起来应该类似,除了使用<code>import</code>语句(这些语句提取了在挑战中为您提供的所有依赖项)。 “使用npm管理包”部分更详细地介绍了npm。最后,编写React和Redux代码通常需要一些配置。这可能很快变得复杂。如果您有兴趣在自己的机器上进行实验,可以配置<a id="CRA" target="_blank" href="https://github.com/facebookincubator/create-react-app">Create React App</a>并准备就绪。或者,您可以在CodePen中启用Babel作为JavaScript预处理器,将React和ReactDOM添加为外部JavaScript资源,并在那里工作。 </section>
|
||||
<section id='description'>
|
||||
恭喜你完成了 React 和 Redux 的所有课程!在继续前进前,还有一点值得我们注意。通常,我们不会在这样的编辑器中编写 React 应用代码。如果你在自己的计算机上使用 npm 和文件系统,这个挑战可让你一瞥 React 应用的语法之貌。除了使用<code>import</code>语句(这些语句引入了各挑战中提供的所有依赖关系),其代码看起来类似。“管理包(含 npm)”这一节更详细地介绍了 npm。
|
||||
最后,写 React 和 Redux 的代码通常需要一些配置,且很快会变得复杂起来。如果你想在自己的机器上尝试写代码,点击链接
|
||||
<a id='CRA' target ='_blank' href='https://github.com/facebookincubator/create-react-app'>创建 React App </a>可获取已配置好的现成代码。
|
||||
另一种做法是在 CodePen 中启用 Babel 作为 JavaScript 预处理器,将 React 和 ReactDOM 添加为外部 JavaScript 资源,在那里编写应用。
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">记录消息<code>'Now I know React and Redux!'</code>到控制台。 </section>
|
||||
<section id='instructions'>
|
||||
把<code>'Now I know React and Redux!'</code>这一消息输出到控制台。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: 消息<code>Now I know React and Redux!</code>应该登录到控制台。
|
||||
- text: <code>Now I know React and Redux!</code>这一消息应输出到控制台。
|
||||
testString: getUserInput => assert(/console\s*\.\s*log\s*\(\s*('|"|`)Now I know React and Redux!\1\s*\)/.test(getUserInput('index')));
|
||||
|
||||
```
|
||||
@ -52,7 +59,7 @@ tests:
|
||||
// document.getElementById('root')
|
||||
// );
|
||||
|
||||
// change code below this line
|
||||
// 请在本行以下添加你的代码
|
||||
|
||||
```
|
||||
|
||||
@ -65,8 +72,9 @@ tests:
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
// solution required
|
||||
console.log('Now I know React and Redux!');
|
||||
```
|
||||
|
||||
/section>
|
||||
</section>
|
||||
|
@ -3,28 +3,41 @@ id: 5a24c314108439a4d4036144
|
||||
title: Use Provider to Connect Redux to React
|
||||
challengeType: 6
|
||||
isRequired: false
|
||||
videoUrl: ''
|
||||
localeTitle: 使用Provider将Redux连接到React
|
||||
forumTopicId: 301435
|
||||
localeTitle: 使用 Provider 连接 Redux 和 React
|
||||
---
|
||||
|
||||
## Description
|
||||
<section id="description">在上一个挑战中,您创建了一个Redux存储来处理messages数组并创建了一个用于添加新消息的操作。下一步是提供对Redux存储的React访问以及分派更新所需的操作。 React Redux提供了<code>react-redux</code>包来帮助完成这些任务。 React Redux提供了一个小API,它有两个关键特性: <code>Provider</code>和<code>connect</code> 。另一个挑战包括<code>connect</code> 。 <code>Provider</code>是React Redux的一个包装组件,它包装了你的React应用程序。然后,此包装器允许您访问整个组件树中的Redux <code>store</code>和<code>dispatch</code>功能。 <code>Provider</code>需要两个道具,Redux商店和应用程序的子组件。为App组件定义<code>Provider</code>可能如下所示: <blockquote> <Provider store = {store}> <br> <应用/> <br> </提供商> </blockquote></section>
|
||||
<section id='description'>
|
||||
在上一挑战中,你创建了 Redux store 和 action,分别用于处理消息数组和添加新消息。下一步要为 React 提供访问 Redux store 及发起更新所需的 actions。<code>react-redux</code>包可帮助我们完成这些任务。
|
||||
React Redux 提供的 API 有两个关键的功能:<code>Provider</code>和<code>connect</code>。你会在另一个挑战中学<code>connect</code>。<code>Provider</code>是 React Redux 包装 React 应用的 wrapper 组件,它允许你访问整个组件树中的 Redux<code>store</code>及<code>dispatch(分发)</code>方法。<code>Provider</code>需要两个 props:Redux store 和 APP 应用的子组件。用于 APP 组件的<code>Provider</code>可这样定义:
|
||||
|
||||
```jsx
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
</Provider>
|
||||
```
|
||||
|
||||
</section>
|
||||
|
||||
## Instructions
|
||||
<section id="instructions">代码编辑器现在显示过去几个挑战中的所有Redux和React代码。它包括Redux存储,操作和<code>DisplayMessages</code>组件。唯一的新部分是底部的<code>AppWrapper</code>组件。使用此顶级组件从<code>ReactRedux</code>呈现<code>Provider</code> ,并将Redux存储作为prop传递。然后将<code>DisplayMessages</code>组件渲染为子级。完成后,您应该看到React组件呈现给页面。 <strong>注意:</strong> React Redux在此处可用作全局变量,因此您可以使用点表示法访问提供程序。编辑器中的代码利用了这一点并将其设置为一个常量<code>Provider</code>供您在<code>AppWrapper</code>渲染方法中使用。 </section>
|
||||
<section id='instructions'>
|
||||
此时,编辑器上显示的是过去几个挑战中所有代码,包括 Redux store、actions、<code>DisplayMessages</code>组件。新出现的代码是底部的<code>AppWrapper</code>组件,这个顶级组件可用于渲染<code>ReactRedux</code>的<code>Provider</code>,并把 Redux 的 store 作为 props 传入。接着,渲染<code>DisplayMessages</code>为子组件。完成这些任务后,你会看到 React 组件渲染到页面上。
|
||||
<strong>注意:</strong> React Redux 在此可作全局变量,因此你可通过点号表示法访问 Provider。利用这一点,编辑器上的代码把<code>Provider</code>设置为常量,便于你在<code>AppWrapper</code>渲染方法中使用。
|
||||
</section>
|
||||
|
||||
## Tests
|
||||
<section id='tests'>
|
||||
|
||||
```yml
|
||||
tests:
|
||||
- text: <code>AppWrapper</code>应该渲染。
|
||||
- text: <code>AppWrapper</code>应渲染。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })());
|
||||
- text: <code>Provider</code>包装器组件应该具有传递给它的<code>store</code>支柱,等于Redux存储。
|
||||
- text: <code>Provider</code>组件应传入相当于 Redux store 的<code>store</code>参数。
|
||||
testString: getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/\s/g,'').includes('<Providerstore={store}>'); })());
|
||||
- text: <code>DisplayMessages</code>应该呈现为<code>AppWrapper</code> 。
|
||||
- text: <code>DisplayMessages</code>应渲染为<code>AppWrapper</code>的子组件。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })());
|
||||
- text: <code>DisplayMessages</code>组件应该呈现h2,input,button和<code>ul</code>元素。
|
||||
- text: <code>DisplayMessages</code>组件应渲染 h2、input、button、<code>ul</code>四个元素。
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('div').length === 1 && mockedComponent.find('h2').length === 1 && mockedComponent.find('button').length === 1 && mockedComponent.find('ul').length === 1; })());
|
||||
|
||||
```
|
||||
@ -37,7 +50,7 @@ tests:
|
||||
<div id='jsx-seed'>
|
||||
|
||||
```jsx
|
||||
// Redux Code:
|
||||
// Redux 代码:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
@ -63,13 +76,13 @@ const messageReducer = (state = [], action) => {
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React Code:
|
||||
// React 代码:
|
||||
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: ",
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
@ -83,7 +96,7 @@ class DisplayMessages extends React.Component {
|
||||
submitMessage() {
|
||||
const currentMessage = this.state.input;
|
||||
this.setState({
|
||||
input: ",
|
||||
input: '',
|
||||
messages: this.state.messages.concat(currentMessage)
|
||||
});
|
||||
}
|
||||
@ -111,11 +124,10 @@ class DisplayMessages extends React.Component {
|
||||
const Provider = ReactRedux.Provider;
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
// render the Provider here
|
||||
// 在此渲染 Provider
|
||||
|
||||
// change code above this line
|
||||
// 请在本行以上添加你的代码
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</div>
|
||||
@ -125,7 +137,7 @@ class AppWrapper extends React.Component {
|
||||
<div id='jsx-teardown'>
|
||||
|
||||
```js
|
||||
console.info('after the test');
|
||||
ReactDOM.render(<AppWrapper />, document.getElementById('root'))
|
||||
```
|
||||
|
||||
</div>
|
||||
@ -135,8 +147,89 @@ console.info('after the test');
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
```js
|
||||
// solution required
|
||||
```
|
||||
|
||||
/section>
|
||||
```js
|
||||
// Redux Code:
|
||||
const ADD = 'ADD';
|
||||
|
||||
const addMessage = (message) => {
|
||||
return {
|
||||
type: ADD,
|
||||
message
|
||||
}
|
||||
};
|
||||
|
||||
const messageReducer = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case ADD:
|
||||
return [
|
||||
...state,
|
||||
action.message
|
||||
];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const store = Redux.createStore(messageReducer);
|
||||
|
||||
// React Code:
|
||||
|
||||
class DisplayMessages extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
input: '',
|
||||
messages: []
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submitMessage = this.submitMessage.bind(this);
|
||||
}
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
input: event.target.value
|
||||
});
|
||||
}
|
||||
submitMessage() {
|
||||
const currentMessage = this.state.input;
|
||||
this.setState({
|
||||
input: '',
|
||||
messages: this.state.messages.concat(currentMessage)
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Type in a new Message:</h2>
|
||||
<input
|
||||
value={this.state.input}
|
||||
onChange={this.handleChange}/><br/>
|
||||
<button onClick={this.submitMessage}>Submit</button>
|
||||
<ul>
|
||||
{this.state.messages.map( (message, idx) => {
|
||||
return (
|
||||
<li key={idx}>{message}</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Provider = ReactRedux.Provider;
|
||||
|
||||
class AppWrapper extends React.Component {
|
||||
// change code below this line
|
||||
render() {
|
||||
return (
|
||||
<Provider store = {store}>
|
||||
<DisplayMessages/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
// change code above this line
|
||||
};
|
||||
```
|
||||
</section>
|
||||
|
Reference in New Issue
Block a user