diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/connect-redux-to-react.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/connect-redux-to-react.chinese.md index ec2b351d09..0672e8fb9a 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/connect-redux-to-react.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/connect-redux-to-react.chinese.md @@ -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 -
现在您已经编写了mapStateToProps()mapDispatchToProps()函数,您可以使用它们将statedispatch映射到您的某个React组件的props 。 React Redux的connect方法可以处理此任务。此方法采用两个可选参数: mapStateToProps()mapDispatchToProps() 。它们是可选的,因为您可能拥有只需要访问state但不需要分派任何操作的组件,反之亦然。要使用此方法,请将函数作为参数传递,并立即使用组件调用结果。这种语法有点不寻常,看起来像: connect(mapStateToProps, mapDispatchToProps)(MyComponent) 注意:如果要省略connect方法的其中一个参数,则在其位置传递null
+
+既然写了mapStateToProps()mapDispatchToProps()两个函数,现在你可以用它们来把statedispatch映射到 React 组件的props了。React Redux 的connect方法可以完成这个任务。此方法有mapStateToProps()mapDispatchToProps()两个可选参数,它们是可选的,原因是你的组件可能仅需要访问状态但不需要分发任何 actions,反之亦然。 +为了使用此方法,需要传入函数参数并在调用时传入组件。这种语法有些不寻常,如下所示: +connect(mapStateToProps, mapDispatchToProps)(MyComponent) +注意: 如果要省略connect方法中的某个参数,则应当用null替换这个参数。 +
## Instructions -
代码编辑器具有mapStateToProps()mapDispatchToProps()函数以及一个名为Presentational的新React组件。使用ReactRedux全局对象中的connect方法将此组件连接到Redux,并立即在Presentational组件上调用它。将结果分配给名为ConnectedComponent的新const ,该const表示连接的组件。就是这样,现在你已经连接到Redux了!尝试将connect的参数更改为null并观察测试结果。
+
+在编辑器上有两个函数:mapStateToProps()mapDispatchToProps(),还有一个叫Presentational的 React 组件。用ReactRedux全局对象中的connect方法将此组件连接到 Redux,并立即在Presentational组件中调用,把结果赋值给一个名为ConnectedComponent的代表已连接组件的新常量。大功告成!你已成功把 React 连接到 Redux!尝试更改任何一个connect参数为null并观察测试结果。 +
## Tests
```yml tests: - - text: Presentational组件应该呈现。 + - text: 应渲染Presentational组件。 testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })()); - - text: Presentational组件应通过connect接收prop messages 。 + - text: Presentational组件应通过connect接收一个messages属性。 testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); const props = mockedComponent.find('Presentational').props(); return props.messages === '__INITIAL__STATE__'; })()); - - text: Presentational组件应通过connect接收prop submitNewMessage 。 + - text: Presentational组件应通过connect接收一个submitNewMessage属性。 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;
```js -console.info('after the test'); + +const store = Redux.createStore( + (state = '__INITIAL__STATE__', action) => state +); +class AppWrapper extends React.Component { + render() { + return ( + + + + ); + } +}; +ReactDOM.render(, document.getElementById('root')) ```
@@ -87,8 +107,43 @@ console.info('after the test'); ## 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

This is a Presentational Component

+ } +}; + +const connect = ReactRedux.connect; +// change code below this line + +const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(Presentational); + ``` -/section> +
diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/connect-redux-to-the-messages-app.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/connect-redux-to-the-messages-app.chinese.md index 14e1603708..7e3b003be3 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/connect-redux-to-the-messages-app.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/connect-redux-to-the-messages-app.chinese.md @@ -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 -
现在您已了解如何使用connect将React连接到Redux,您可以将您学到的知识应用于处理消息的React组件。在上一课中,您连接到Redux的组件名为Presentational ,这不是任意的。该术语通常是指未直接连接到Redux的React组件。他们只负责UI的呈现,并根据他们收到的道具来执行此操作。相比之下,容器组件连接到Redux。这些通常负责将操作分派给商店,并且经常将商店状态作为道具传递给子组件。
+
+知道connect怎么实现 React 和 Redux 的连接后,我们可以在 React 组件中应用上面学到的内容。 +在上一课,连接到 Redux 的组件命名为Presentational,这个命名不是任意的,这样的术语通常是指未直接连接到 Redux 的 React 组件,他们只负责执行接收 props 的函数来实现 UI 的呈现。与上一挑战相比,本挑战需要把容器组件连接到 Redux。这些组件通常负责把 actions 分派给 store,且经常给子组件传入 store state 属性。 +
## Instructions -
到目前为止,代码编辑器包含了您在本节中编写的所有代码。唯一的变化是React组件被重命名为Presentational 。创建一个名为Container的常量中保存的新组件,该常量使用connectPresentational组件连接到Redux。然后,在AppWrapper ,渲染React Redux Provider组件。将Redux store Provider作为道具传递,并将Container作为子项呈现。设置完所有内容后,您将再次看到应用程序呈现给页面的消息。
+
+到目前为止,我们的编辑器上已包含了整个章节的代码,唯一不同的是,React 组件被重新命名为Presentational,即展示层组件。创建一个新组件,保存在名为Container的常量中。这个常量用connectPresentational组件和 Redux 连接起来。然后,在AppWrapper中渲染 React Redux 的Provider组件,给Provider传入 Reduxstore属性并渲染Container为子组件。完成这些,消息 app 应用会再次渲染页面。 +
## Tests
```yml tests: - - text: AppWrapper应该呈现给页面。 + - text: AppWrapper应渲染该页面。 testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })()); - - text: Presentational组件应呈现h2inputbuttonul元素。 + - text: Presentational组件应渲染h2inputbuttonul四个元素。 testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })()); - - text: Presentational组件应呈现h2inputbuttonul元素。 + - text: Presentational组件应渲染h2inputbuttonul四个元素。 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: Presentational组件应该从Redux商店接收messages作为道具。 + - text: Presentational组件应接收 Redux store 的消息属性。 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: Presentational组件应该接收submitMessage操作创建者作为prop。 + - text: Presentational组件应接收创建 action 的函数submitMessage属性。 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 ( +
+

Type in a new Message:

+
+ +
    + {this.state.messages.map( (message, idx) => { + return ( +
  • {message}
  • + ) + }) + } +
+
+ ); + } +}; + +// 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); + } +}; +``` + + + + +### After Test +
+ +```js +ReactDOM.render(, document.getElementById('root')) +``` + +
+ +
+ +## 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 ( + + + + ); } }; - ``` - - - - -### After Test -
- -```js -console.info('after the test'); -``` - -
-
- -## Solution -
- -```js -// solution required -``` - -/section> diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/extract-local-state-into-redux.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/extract-local-state-into-redux.chinese.md index af3753a7cb..f266074318 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/extract-local-state-into-redux.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/extract-local-state-into-redux.chinese.md @@ -3,38 +3,43 @@ id: 5a24c314108439a4d4036149 title: Extract Local State into Redux challengeType: 6 isRequired: false -videoUrl: '' -localeTitle: 将本地状态提取到Redux +forumTopicId: 301428 +localeTitle: 将局部状态提取到 Redux 中 --- ## Description -
你几乎完成!回想一下,您编写了所有Redux代码,以便Redux可以控制React消息应用程序的状态管理。现在Redux已连接,您需要从Presentational组件中提取状态管理并进入Redux。目前,您已连接Redux,但您正在Presentational组件中本地处理状态。
+
+胜利就在眼前了!请回顾一下为管理 React messages app 的状态写的 Redux 代码。现在有了连接好的 Redux,你还要从Presentational组件中提取状态管理到 Redux,在Presentational组件内处理本地状态。 +
## Instructions -
Presentational组件中,首先,删除本地state中的messages属性。这些消息将由Redux管理。接下来,修改submitMessage()方法,以便从this.props调度submitNewMessage() ,并将当前消息输入作为参数传入本地state 。因为你删除messages从本地状态,删除messages从调用属性this.setState()在这里。最后,修改render()方法,使其映射到从props而不是state接收的消息。一旦做出这些更改,除了Redux管理状态之外,应用程序将继续运行相同的功能。这个例子也说明组件可以如何具有本地state :你的组件还是本地跟踪用户输入自己的state 。您可以看到Redux如何在React之上提供有用的状态管理框架。您最初仅使用React的本地状态获得了相同的结果,这通常可以通过简单的应用程序实现。但是,随着您的应用程序变得越来越大,越来越复杂,您的状态管理也是如此,这就是Redux解决的问题。
+
+在Presentational组件中,先删除本地state中的messages属性,被删的 messages 将由 Redux 管理。接着,修改submitMessage()方法,使该方法从this.props那里分发submitNewMessage();从本地state中传入当前消息输入作为参数。因本地状态删除了messages属性,所以在调用this.setState()时也要删除该属性。最后,修改render()方法,使其所映射的消息是从props接收的,而不是state +完成这些更改后,我们的应用会实现 Redux 管理应用的状态,但它继续运行着相同的功能。此示例还阐明了组件获得本地状态的方式,即在自己的状态中继续跟踪用户本地输入。由此可见,Redux 为 React 提供了很有用的状态管理框架。先前,你仅使用 React 的本地状态也实现了相同的结果,这在应付简单的应用时通常是可行的。但是,随着应用变得越来越大,越来越复杂,应用的状态管理也变得非常困难,Redux 就是为解决这样的问题而诞生的。 +
## Tests
```yml tests: - - text: AppWrapper应该呈现给页面。 + - text: AppWrapper应该渲染该到页面。 testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })()); - - text: Presentational组件应呈现h2inputbuttonul元素。 + - text: Presentational 应该渲染到页面上. testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })()); - - text: Presentational组件应呈现h2inputbuttonul元素。 + - text: Presentational组件应渲染h2inputbuttonul四个元素。 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: Presentational组件应该从Redux商店接收messages作为道具。 + - text: Presentational组件应接收 Redux store 的消息属性。 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: Presentational组件应该接收submitMessage操作创建者作为prop。 + - text: Presentational组件应接收创建 action 的函数submitMessage属性。 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: Presentational组件的状态应包含一个属性input ,该属性初始化为空字符串。 + - text: Presentational组件的状态应包含一个初始化为空字符串的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: 输入input元素应该更新Presentational组件的状态。 + - text: 键入input元素应更新Presentational组件的状态。 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: 在Presentational组件上调度submitMessage应更新Redux存储并清除本地状态的输入。 + - text: 在Presentational组件上 dispatch submitMessage应更新 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: Presentational组件应该呈现来自Redux存储的messages 。 + - text: Presentational组件应渲染 Redux store 中的messages 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 ( + + + + ); + } +}; +``` + + + + +### After Test +
+ +```js +ReactDOM.render(, document.getElementById('root')) +``` + +
+ +
+ +## 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 ( +
+

Type in a new Message:

+
+ +
    + {this.props.messages.map( (message, idx) => { + return ( +
  • {message}
  • + ) + }) + } +
+
+ ); + } +}; // Change code above this line const mapStateToProps = (state) => { @@ -142,28 +262,6 @@ class AppWrapper extends React.Component { ); } }; - ``` - - - -### After Test -
- -```js -console.info('after the test'); -``` - -
-
- -## Solution -
- -```js -// solution required -``` - -/section> diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/extract-state-logic-to-redux.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/extract-state-logic-to-redux.chinese.md index 4e14b32560..1fe03d4bc5 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/extract-state-logic-to-redux.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/extract-state-logic-to-redux.chinese.md @@ -3,32 +3,37 @@ id: 5a24c314108439a4d4036143 title: Extract State Logic to Redux challengeType: 6 isRequired: false -videoUrl: '' -localeTitle: 将状态逻辑提取到Redux +forumTopicId: 301429 +localeTitle: 提取状态逻辑给 Redux --- ## Description -
现在您已完成React组件,您需要将其在其state本地执行的逻辑移动到Redux中。这是将简单的React应用程序连接到Redux的第一步。您的应用程序的唯一功能是将用户的新消息添加到无序列表中。该示例很简单,以演示React和Redux如何协同工作。
+
+完成 React 组件后,我们需要把在本地状态执行的逻辑移到 Redux 中,这是为小规模 React 应用添加 Redux 的第一步。该应用的唯一功能是把用户的新消息添加到无序列表中。下面我们用简单的示例来演示 React 和 Redux 之间的配合。 +
## Instructions -
首先,定义一个动作类型'ADD'并将其设置为const ADD 。接下来,定义一个动作创建器addMessage() ,它创建添加消息的动作。您需要将message给此操作创建者,并在返回的action包含该消息。然后创建一个名为messageReducer()的reducer来处理消息的状态。初始状态应该等于空数组。此reducer应向状态中保存的消息数组添加消息,或返回当前状态。最后,创建Redux存储并将其传递给reducer。
+
+首先,定义 action 的类型 'ADD',将其设置为常量ADD。接着,定义创建 action 的函数addMessage(),用该函数创建添加消息的 action,把message传给创建 action 的函数并返回包含该消息的action +接着,创建名为messageReducer()的 reducer 方法,为这些消息处理状态。初始状态应为空数组。reducer 向状态中的消息数组添加消息,或返回当前状态。最后,创建 Redux store 并传给 reducer。 +
## Tests
```yml tests: - - text: const ADD应该存在并保持一个等于字符串ADD的值 + - text: 应存在一个值为字符串ADD的常量ADD。 testString: assert(ADD === 'ADD'); - - text: 动作创建者addMessage应返回type等于ADD的对象,并且消息等于传入的消息。 + - text: 创建 action 的函数addMessage应返回type等于ADD的对象,其返回的消息即被传入的消息。 testString: assert((function() { const addAction = addMessage('__TEST__MESSAGE__'); return addAction.type === ADD && addAction.message === '__TEST__MESSAGE__'; })()); - - text: messageReducer应该是一个函数。 + - text: messageReducer应是一个函数。 testString: assert(typeof messageReducer === 'function'); - - text: 存储应该存在并且初始状态设置为空数组。 + - text: 存在一个 store 且其初始状态为空数组。 testString: assert((function() { const initialState = store.getState(); return typeof store === 'object' && initialState.length === 0; })()); - - text: 对商店调度addMessage应该addMessage向状态中保存的消息数组添加新消息。 + - text: 分发addMessage到 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: 如果使用任何其他操作调用, messageReducer应返回当前状态。 + - text: messageReducer被其它任何 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:
```jsx -// define ADD, addMessage(), messageReducer(), and store here: +// 请在此处定义 ADD、addMessage()、messageReducer()、store: ``` @@ -54,8 +59,31 @@ tests: ## 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> +
+ diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/getting-started-with-react-redux.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/getting-started-with-react-redux.chinese.md index a4c6ed53a2..d60f70d71b 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/getting-started-with-react-redux.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/getting-started-with-react-redux.chinese.md @@ -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 -
这一系列挑战介绍了如何将Redux与React一起使用。首先,这里回顾一下每种技术的一些关键原则。 React是一个您提供数据的视图库,然后以高效,可预测的方式呈现视图。 Redux是一个状态管理框架,可用于简化应用程序状态的管理。通常,在React Redux应用程序中,您可以创建一个Redux存储库来管理整个应用程序的状态。您的React组件仅订阅商店中与其角色相关的数据。然后,直接从React组件调度操作,然后触发存储更新。虽然React组件可以在本地管理自己的状态,但是当您拥有复杂的应用程序时,通常最好将应用程序状态保存在Redux的单个位置。当单个组件可能仅具有特定于其的本地状态时,存在例外情况。最后,因为Redux不是设计用于开箱即用的React,所以你需要使用react-redux包。它为您提供了Redux的传递方式state ,并dispatch到你的反应的组分作为props 。在接下来的几个挑战中,首先,您将创建一个简单的React组件,允许您输入新的文本消息。这些将添加到视图中显示的数组中。这应该是您在React课程中学到的内容的一个很好的回顾。接下来,您将创建一个Redux存储和操作来管理messages数组的状态。最后,您将使用react-redux将Redux存储与您的组件连接,从而将本地状态提取到Redux存储中。
+
+这一系列挑战介绍的是 Redux 和 React 的配合,我们先来回顾一下这两种技术的关键原则是什么。React 是提供数据的视图库,能以高效、可预测的方式渲染视图。Redux 是状态管理框架,可用于简化 APP 应用状态的管理。在 React Redux app 应用中,通常可创建单一的 Redux store 来管理整个应用的状态。React 组件仅订阅 store 中与其角色相关的数据,你可直接从 React 组件中分发 actions 以触发 store 对象的更新。 +React 组件可以在本地管理自己的状态,但是对于复杂的应用来说,它的状态最好是用 Redux 保存在单一位置,有特定本地状态的独立组件例外。最后一点是,Redux 没有内置的 React,需要安装react-redux包,通过这个方式把 Redux 的statedispatch作为props传给组件。 +在接下来的挑战中,先要创建一个可输入新文本消息的 React 组件,添加这些消息到数组里,在视图上显示数组。接着,创建 Redux store 和 actions 来管理消息数组的状态。最后,使用react-redux连接 Redux store 和组件,从而将本地状态提取到 Redux store 中。 +
## Instructions -
DisplayMessages组件开始。一个构造添加到该组件,并使用具有两个属性的状态初始化: input ,其被设置为一个空字符串,和messages ,这是设置为空数组。
+
+创建DisplayMessages组件,把构造函数添加到此组件中,使用含两个属性的状态初始化该组件,这两个属性为:input(设置为空字符串),messages(设置为空数组)。 +
## Tests
```yml tests: - - text: DisplayMessages组件应呈现一个空的div元素。 + - text: DisplayMessages组件应渲染空的div元素。 testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })()); - - text: 应该使用super正确调用DisplayMessages构造函数,传入props 。 + - text: DisplayMessages组件的构造函数应调用super,传入props。 testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })()); - - text: 'DisplayMessages组件的初始状态应等于{input: "", messages: []} 。' + - text: 'DisplayMessages组件的初始状态应是{input: "", messages: []}。' 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
} }; - ```
@@ -53,7 +58,7 @@ class DisplayMessages extends React.Component {
```js -console.info('after the test'); +ReactDOM.render(, document.getElementById('root')) ```
@@ -63,8 +68,21 @@ console.info('after the test'); ## Solution
+ ```js -// solution required +class DisplayMessages extends React.Component { + constructor(props) { + super(props); + this.state = { + input: '', + messages: [] + } + } + render() { + return
+ } +}; ``` -/section> +
+ diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/manage-state-locally-first.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/manage-state-locally-first.chinese.md index 520e500e23..ff47176a5e 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/manage-state-locally-first.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/manage-state-locally-first.chinese.md @@ -3,33 +3,41 @@ id: 5a24c314108439a4d4036142 title: Manage State Locally First challengeType: 6 isRequired: false -videoUrl: '' -localeTitle: 在当地管理国家 +forumTopicId: 301431 +localeTitle: 首先在本地管理状态 --- ## Description -
在这里,您将完成创建DisplayMessages组件。
+
+这一关的任务是完成DisplayMessages组件的创建。 +
## Instructions -
首先,在render()方法中,让组件呈现input元素, button元素和ul元素。当input元素改变时,它应该触发handleChange()方法。此外, input元素应该呈现处于组件状态的input值。 button元素应在单击时触发submitMessage()方法。其次,写下这两种方法。该handleChange()方法应该更新input与用户正在打字。 submitMessage()方法应将当前消息(存储在input )连接到本地状态的messages数组,并清除input的值。最后,使用ul映射messages数组并将其作为li元素列表呈现给屏幕。
+
+首先,在render()方法中,让组件渲染inputbuttonul三个元素。input元素的改变会触发handleChange()方法。此外,input元素会渲染组件状态中input的值。点击按钮button需触发submitMessage()方法。 +接着,写出这两种方法。handleChange()方法会更新input为用户正在输入的内容。submitMessage()方法把当前存储在input的消息与本地状态的messages数组连接起来,并清除input的值。 +最后,在ul中展示messages数组,其中每个元素内容需放到li元素内。 +
## Tests
```yml tests: - - text: 'DisplayMessages组件应使用等于{ input: "", messages: [] }的状态进行初始化。' - 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: DisplayMessages组件应该呈现包含h2元素, button元素, ul元素和li元素作为子元素的div 。 - 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 DisplayMessages component should render a div containing an h2 element, a button element, a ul element, and li elements as children."); }; ' - - text: input元素应该以本地状态呈现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 testValue = "__TEST__EVENT__INPUT"; const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); assert(updated.find("input").props().value === testValue, "The input element should render the value of input in local state."); }; ' - - text: 调用方法handleChange应该将状态中的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 = "__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 handleChange should update the input value in state to the current input."); }; ' - - text: 单击“ Add message按钮应调用方法submitMessage ,该方法submitMessage当前input添加到状态中的messages数组。 - 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 Add message button should call the method submitMessage which should add the current input to the messages array in state."); }; ' - - text: submitMessage方法应该清除当前输入。 - 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 submitMessage method should clear the current input."); }; ' + - text: 'DisplayMessages组件的初始状态应是{ input: "", messages: [] }。' + 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: DisplayMessages组件应渲染含h2buttonulli四个子元素的div。 + 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: input元素应渲染本地状态中的input值。 + testString: assert(code.match(/this\.state\.messages\.map/g)); + - text: 调用handleChange方法时应更新状态中的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 testValue = ''__TEST__EVENT__INPUT''; const changed = () => { causeChange(mockedComponent, testValue); return waitForIt(() => mockedComponent )}; const updated = await changed(); assert(updated.find(''input'').props().value === testValue); }; ' + - text: 单击Add message按钮应调用submitMessage方法,添加当前输入到状态中的消息数组。 + 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: submitMessage方法应清除当前输入。 + 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 submitMessage 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 (
-

Type in a new Message:

- { /* render an input, button, and ul here */ } +

键入新 Message

+ { /* 在此渲染 input、button、ul*/ } - { /* change code above this line */ } + { /* 请在本行以上添加你的代码 */ }
); } }; - ```
@@ -72,7 +79,7 @@ class DisplayMessages extends React.Component {
```js -console.info('after the test'); +ReactDOM.render(, document.getElementById('root')) ```
@@ -82,8 +89,50 @@ console.info('after the test'); ## 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 ( +
+

Type in a new Message:

+
+ +
    + {this.state.messages.map( (message, idx) => { + return ( +
  • {message}
  • + ) + }) + } +
+
+ ); + } +}; ``` -/section> +
diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/map-dispatch-to-props.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/map-dispatch-to-props.chinese.md index 2df2e3a248..a661cc16a9 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/map-dispatch-to-props.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/map-dispatch-to-props.chinese.md @@ -3,28 +3,42 @@ id: 5a24c314108439a4d4036146 title: Map Dispatch to Props challengeType: 6 isRequired: false -videoUrl: '' -localeTitle: 将调度映射到道具 +forumTopicId: 301432 +localeTitle: 映射 Dispatch 到 Props --- ## Description -
mapDispatchToProps()函数用于为React组件提供特定的操作创建器,以便它们可以针对Redux存储分派操作。它的结构与您在上一次挑战中编写的mapStateToProps()函数类似。它返回一个对象,该对象将调度操作映射到属性名称,后者成为组件props 。但是,每个属性都返回一个函数,该函数使用动作创建者和任何相关的动作数据来调用dispatch ,而不是返回一个state 。您可以访问此dispatch因为它在您定义函数时作为参数传递给mapDispatchToProps() ,就像您将state传递给mapStateToProps() 。在幕后,阵营终极版使用终极版的store.dispatch()进行这些分派mapDispatchToProps()这类似于它将store.subscribe()用于映射到state组件。例如,您有一个loginUser()动作创建者,它将username作为动作有效负载。从mapDispatchToProps()为此动作创建者返回的对象看起来像:
{
submitLoginUser:function(username){
调度(loginUser(用户名));
}
}
+
+mapDispatchToProps()函数可为 React 组件提供特定的创建 action 的函数,以便组件可 dispatch actions,从而更改 Redux store 中的数据。该函数的结构跟上一挑战中的mapStateToProps()函数相似,它返回一个对象,把 dispatch actions 映射到属性名上,该属性名成为props。然而,每个属性都返回一个用 action creator 及与 action 相关的所有数据调用dispatch的函数,而不是返回state的一部分。你可以访问dispatch,因为在定义函数时,我们以参数形式把它传入mapDispatchToProps()了,这跟state传入mapDispatchToProps()是一样的。在幕后,React Redux 用 Redux 的store.dispatch()来管理这些含mapDispatchToProps()的dispatches,这跟它使用store.subscribe()来订阅映射到state的组件的方式类似。 +例如,创建 action 的函数loginUser()username作为 action payload,mapDispatchToProps()返回给创建 action 的函数的对象如下: + +```jsx +{ + submitLoginUser: function(username) { + dispatch(loginUser(username)); + } +} +``` + +
## Instructions -
代码编辑器提供了一个名为addMessage()的动作创建器。编写函数mapDispatchToProps() ,将dispatch作为参数,然后返回一个对象。该对象应该将一个属性submitNewMessage设置为dispatch函数,该函数在调度addMessage()时为新消息添加一个参数。
+
+编辑器上提供的是创建 action 的函数addMessage()。写出接收dispatch为参数的函数mapDispatchToProps(),返回一个 dispatch 函数对象,其属性为submitNewMessage。该函数在 dispatch addMessage()时为新消息提供一个参数。 +
## Tests
```yml tests: - - text: addMessage应该返回一个带有键typemessage的对象。 + - text: addMessage应返回含typemessage两个键的对象。 testString: assert((function() { const addMessageTest = addMessage(); return ( addMessageTest.hasOwnProperty('type') && addMessageTest.hasOwnProperty('message')); })()); - - text: mapDispatchToProps应该是一个函数。 + - text: mapDispatchToProps应为函数。 testString: assert(typeof mapDispatchToProps === 'function'); - - text: mapDispatchToProps应该返回一个对象。 + - text: mapDispatchToProps应返回一个对象。 testString: assert(typeof mapDispatchToProps() === 'object'); - - text: 使用submitNewMessagemapDispatchToProps调度addMessage应该向调度函数返回一条消息。 + - text: 从mapDispatchToProps通过submitNewMessage分发addMessage,应向 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
+ ```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> +
diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/map-state-to-props.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/map-state-to-props.chinese.md index 0800a60bfb..dfda45ebc1 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/map-state-to-props.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/map-state-to-props.chinese.md @@ -3,28 +3,34 @@ id: 5a24c314108439a4d4036145 title: Map State to Props challengeType: 6 isRequired: false -videoUrl: '' -localeTitle: 将状态映射到道具 +forumTopicId: 301433 +localeTitle: 映射 State 到 Props --- ## Description -
Provider组件允许您为React组件提供statedispatch ,但您必须准确指定所需的状态和操作。这样,您可以确保每个组件只能访问所需的状态。您可以通过创建两个函数来完成此任务: mapStateToProps()mapDispatchToProps() 。在这些函数中,您可以声明要访问的状态段以及需要分配的操作创建者。一旦这些功能到位,您将看到如何使用React Redux connect方法在另一个挑战中将它们连接到您的组件。 注意:在幕后,React Redux使用store.subscribe()方法实现mapStateToProps()
+
+Provider可向 React 组件提供statedispatch,但你必须确切地指定所需要的 state 和 actions,以确保每个组件只能访问所需的 state。完成这个任务,你需要创建两个函数:mapStateToProps()mapDispatchToProps()。 +在这两个函数中,声明 state 中函数所要访问的部分及需要 dispatch 的创建 action 的函数。完成这些,我们就可以迎接下一个挑战,学习如何使用 React Redux 的connect方法来把函数连接到组件了。 +注意: 在幕后,React Redux 用store.subscribe()方法来实现mapStateToProps()。 +
## Instructions -
创建一个函数mapStateToProps() 。此函数应将state作为参数,然后返回将该状态映射到特定属性名称的对象。您可以通过props访问这些属性。由于此示例将应用程序的整个状态保存在单个数组中,因此您可以将整个状态传递给组件。在要返回的对象中创建属性messages ,并将其设置为state
+
+创建mapStateToProps()函数,以state为参数,然后返回一个对象,该对象把 state 映射到特定属性名上,这些属性能通过props访问组件。由于此示例把 app 应用的整个状态保存在单一数组中,你可把整个状态传给组件。在返回的对象中创建messages属性,并设置为state。 +
## Tests
```yml tests: - - text: const state应该是一个空数组。 + - text: 常量state应为空数组。 testString: assert(Array.isArray(state) && state.length === 0); - - text: mapStateToProps应该是一个函数。 + - text: mapStateToProps应为函数。 testString: assert(typeof mapStateToProps === 'function'); - - text: mapStateToProps应该返回一个对象。 + - text: mapStateToProps应返还一个对象。 testString: assert(typeof mapStateToProps() === 'object'); - - text: 将数组作为状态传递给mapStateToProps应该返回分配给messages键的数组。 + - text: 把 state 数组传入mapStateToProps后应返回赋值给messages键的数组。 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
+ ```js -// solution required +const state = []; + +// change code below this line + +const mapStateToProps = (state) => { + return { + messages: state + } +}; ``` -/section> +
diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/moving-forward-from-here.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/moving-forward-from-here.chinese.md index 7b6af75707..bd28af733a 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/moving-forward-from-here.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/moving-forward-from-here.chinese.md @@ -3,22 +3,29 @@ id: 5a24c314108439a4d403614a title: Moving Forward From Here challengeType: 6 isRequired: false -videoUrl: '' +forumTopicId: 301434 localeTitle: 从这里前进 --- ## Description -
恭喜!你完成了React和Redux的课程。在继续之前,最后一项值得指出。通常,您不会在这样的代码编辑器中编写React应用程序。如果您在自己的计算机上使用npm和文件系统,这个挑战可以让您一瞥语法的样子。代码看起来应该类似,除了使用import语句(这些语句提取了在挑战中为您提供的所有依赖项)。 “使用npm管理包”部分更详细地介绍了npm。最后,编写React和Redux代码通常需要一些配置。这可能很快变得复杂。如果您有兴趣在自己的机器上进行实验,可以配置Create React App并准备就绪。或者,您可以在CodePen中启用Babel作为JavaScript预处理器,将React和ReactDOM添加为外部JavaScript资源,并在那里工作。
+
+恭喜你完成了 React 和 Redux 的所有课程!在继续前进前,还有一点值得我们注意。通常,我们不会在这样的编辑器中编写 React 应用代码。如果你在自己的计算机上使用 npm 和文件系统,这个挑战可让你一瞥 React 应用的语法之貌。除了使用import语句(这些语句引入了各挑战中提供的所有依赖关系),其代码看起来类似。“管理包(含 npm)”这一节更详细地介绍了 npm。 +最后,写 React 和 Redux 的代码通常需要一些配置,且很快会变得复杂起来。如果你想在自己的机器上尝试写代码,点击链接 +创建 React App 可获取已配置好的现成代码。 +另一种做法是在 CodePen 中启用 Babel 作为 JavaScript 预处理器,将 React 和 ReactDOM 添加为外部 JavaScript 资源,在那里编写应用。 +
## Instructions -
记录消息'Now I know React and Redux!'到控制台。
+
+把'Now I know React and Redux!'这一消息输出到控制台。 +
## Tests
```yml tests: - - text: 消息Now I know React and Redux!应该登录到控制台。 + - text: Now I know React and Redux!这一消息应输出到控制台。 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
+ ```js -// solution required +console.log('Now I know React and Redux!'); ``` -/section> +
diff --git a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.chinese.md b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.chinese.md index 790a7807aa..1cea322087 100644 --- a/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.chinese.md +++ b/curriculum/challenges/chinese/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.chinese.md @@ -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 -
在上一个挑战中,您创建了一个Redux存储来处理messages数组并创建了一个用于添加新消息的操作。下一步是提供对Redux存储的React访问以及分派更新所需的操作。 React Redux提供了react-redux包来帮助完成这些任务。 React Redux提供了一个小API,它有两个关键特性: Providerconnect 。另一个挑战包括connectProvider是React Redux的一个包装组件,它包装了你的React应用程序。然后,此包装器允许您访问整个组件树中的Redux storedispatch功能。 Provider需要两个道具,Redux商店和应用程序的子组件。为App组件定义Provider可能如下所示:
<Provider store = {store}>
<应用/>
</提供商>
+
+在上一挑战中,你创建了 Redux store 和 action,分别用于处理消息数组和添加新消息。下一步要为 React 提供访问 Redux store 及发起更新所需的 actions。react-redux包可帮助我们完成这些任务。 +React Redux 提供的 API 有两个关键的功能:Providerconnect。你会在另一个挑战中学connectProvider是 React Redux 包装 React 应用的 wrapper 组件,它允许你访问整个组件树中的 Reduxstoredispatch(分发)方法。Provider需要两个 props:Redux store 和 APP 应用的子组件。用于 APP 组件的Provider可这样定义: + +```jsx + + + +``` + +
## Instructions -
代码编辑器现在显示过去几个挑战中的所有Redux和React代码。它包括Redux存储,操作和DisplayMessages组件。唯一的新部分是底部的AppWrapper组件。使用此顶级组件从ReactRedux呈现Provider ,并将Redux存储作为prop传递。然后将DisplayMessages组件渲染为子级。完成后,您应该看到React组件呈现给页面。 注意: React Redux在此处可用作全局变量,因此您可以使用点表示法访问提供程序。编辑器中的代码利用了这一点并将其设置为一个常量Provider供您在AppWrapper渲染方法中使用。
+
+此时,编辑器上显示的是过去几个挑战中所有代码,包括 Redux store、actions、DisplayMessages组件。新出现的代码是底部的AppWrapper组件,这个顶级组件可用于渲染ReactReduxProvider,并把 Redux 的 store 作为 props 传入。接着,渲染DisplayMessages为子组件。完成这些任务后,你会看到 React 组件渲染到页面上。 +注意: React Redux 在此可作全局变量,因此你可通过点号表示法访问 Provider。利用这一点,编辑器上的代码把Provider设置为常量,便于你在AppWrapper渲染方法中使用。 +
## Tests
```yml tests: - - text: AppWrapper应该渲染。 + - text: AppWrapper应渲染。 testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })()); - - text: Provider包装器组件应该具有传递给它的store支柱,等于Redux存储。 + - text: Provider组件应传入相当于 Redux store 的store参数。 testString: getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/\s/g,'').includes(''); })()); - - text: DisplayMessages应该呈现为AppWrapper 。 + - text: DisplayMessages应渲染为AppWrapper的子组件。 testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })()); - - text: DisplayMessages组件应该呈现h2,input,button和ul元素。 + - text: DisplayMessages组件应渲染 h2、input、button、ul四个元素。 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:
```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 + // 请在本行以上添加你的代码 }; - ```
@@ -125,7 +137,7 @@ class AppWrapper extends React.Component {
```js -console.info('after the test'); +ReactDOM.render(, document.getElementById('root')) ```
@@ -135,8 +147,89 @@ console.info('after the test'); ## 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 ( +
+

Type in a new Message:

+
+ +
    + {this.state.messages.map( (message, idx) => { + return ( +
  • {message}
  • + ) + }) + } +
+
+ ); + } +}; + +const Provider = ReactRedux.Provider; + +class AppWrapper extends React.Component { + // change code below this line + render() { + return ( + + + + ); + } + // change code above this line +}; +``` +