mapStateToProps()
和mapDispatchToProps()
函数,您可以使用它们将state
和dispatch
映射到您的某个React组件的props
。 React Redux的connect
方法可以处理此任务。此方法采用两个可选参数: mapStateToProps()
和mapDispatchToProps()
。它们是可选的,因为您可能拥有只需要访问state
但不需要分派任何操作的组件,反之亦然。要使用此方法,请将函数作为参数传递,并立即使用组件调用结果。这种语法有点不寻常,看起来像: connect(mapStateToProps, mapDispatchToProps)(MyComponent)
注意:如果要省略connect
方法的其中一个参数,则在其位置传递null
。 mapStateToProps()
、mapDispatchToProps()
两个函数,现在你可以用它们来把state
和dispatch
映射到 React 组件的props
了。React Redux 的connect
方法可以完成这个任务。此方法有mapStateToProps()
、mapDispatchToProps()
两个可选参数,它们是可选的,原因是你的组件可能仅需要访问状态
但不需要分发任何 actions,反之亦然。
+为了使用此方法,需要传入函数参数并在调用时传入组件。这种语法有些不寻常,如下所示:
+connect(mapStateToProps, mapDispatchToProps)(MyComponent)
+注意: 如果要省略connect
方法中的某个参数,则应当用null
替换这个参数。
+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
并观察测试结果。
+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;
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 属性。
+Presentational
。创建一个名为Container
的常量中保存的新组件,该常量使用connect
将Presentational
组件连接到Redux。然后,在AppWrapper
,渲染React Redux Provider
组件。将Redux store
Provider
作为道具传递,并将Container
作为子项呈现。设置完所有内容后,您将再次看到应用程序呈现给页面的消息。 Presentational
,即展示层组件。创建一个新组件,保存在名为Container
的常量中。这个常量用connect
把Presentational
组件和 Redux 连接起来。然后,在AppWrapper
中渲染 React Redux 的Provider
组件,给Provider
传入 Reduxstore
属性并渲染Container
为子组件。完成这些,消息 app 应用会再次渲染页面。
+AppWrapper
应该呈现给页面。
+ - text: AppWrapper
应渲染该页面。
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })());
- - text: Presentational
组件应呈现h2
, input
, button
和ul
元素。
+ - text: Presentational
组件应渲染h2
、input
、button
、ul
四个元素。
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })());
- - text: Presentational
组件应呈现h2
, input
, button
和ul
元素。
+ - text: Presentational
组件应渲染h2
、input
、button
、ul
四个元素。
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 (
+ Presentational
组件中提取状态管理并进入Redux。目前,您已连接Redux,但您正在Presentational
组件中本地处理状态。 Presentational
组件中提取状态管理到 Redux,在Presentational
组件内处理本地状态。
+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 就是为解决这样的问题而诞生的。
+AppWrapper
应该呈现给页面。
+ - text: AppWrapper
应该渲染该到页面。
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })());
- - text: Presentational
组件应呈现h2
, input
, button
和ul
元素。
+ - text: Presentational
应该渲染到页面上.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('Presentational').length === 1; })());
- - text: Presentational
组件应呈现h2
, input
, button
和ul
元素。
+ - text: Presentational
组件应渲染h2
、input
、button
、ul
四个元素。
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 (
+ state
本地执行的逻辑移动到Redux中。这是将简单的React应用程序连接到Redux的第一步。您的应用程序的唯一功能是将用户的新消息添加到无序列表中。该示例很简单,以演示React和Redux如何协同工作。 状态
执行的逻辑移到 Redux 中,这是为小规模 React 应用添加 Redux 的第一步。该应用的唯一功能是把用户的新消息添加到无序列表中。下面我们用简单的示例来演示 React 和 Redux 之间的配合。
+ADD
。接下来,定义一个动作创建器addMessage()
,它创建添加消息的动作。您需要将message
给此操作创建者,并在返回的action
包含该消息。然后创建一个名为messageReducer()
的reducer来处理消息的状态。初始状态应该等于空数组。此reducer应向状态中保存的消息数组添加消息,或返回当前状态。最后,创建Redux存储并将其传递给reducer。 ADD
。接着,定义创建 action 的函数addMessage()
,用该函数创建添加消息的 action,把message
传给创建 action 的函数并返回包含该消息的action
+接着,创建名为messageReducer()
的 reducer 方法,为这些消息处理状态。初始状态应为空数组。reducer 向状态中的消息数组添加消息,或返回当前状态。最后,创建 Redux store 并传给 reducer。
+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:
react-redux
包。它为您提供了Redux的传递方式state
,并dispatch
到你的反应的组分作为props
。在接下来的几个挑战中,首先,您将创建一个简单的React组件,允许您输入新的文本消息。这些将添加到视图中显示的数组中。这应该是您在React课程中学到的内容的一个很好的回顾。接下来,您将创建一个Redux存储和操作来管理messages数组的状态。最后,您将使用react-redux
将Redux存储与您的组件连接,从而将本地状态提取到Redux存储中。 react-redux
包,通过这个方式把 Redux 的state
和dispatch
作为props
传给组件。
+在接下来的挑战中,先要创建一个可输入新文本消息的 React 组件,添加这些消息到数组里,在视图上显示数组。接着,创建 Redux store 和 actions 来管理消息数组的状态。最后,使用react-redux
连接 Redux store 和组件,从而将本地状态提取到 Redux store 中。
+DisplayMessages
组件开始。一个构造添加到该组件,并使用具有两个属性的状态初始化: input
,其被设置为一个空字符串,和messages
,这是设置为空数组。 DisplayMessages
组件,把构造函数添加到此组件中,使用含两个属性的状态初始化该组件,这两个属性为:input(设置为空字符串),messages
(设置为空数组)。
+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
}
};
-
```
DisplayMessages
组件。 DisplayMessages
组件的创建。
+render()
方法中,让组件呈现input
元素, button
元素和ul
元素。当input
元素改变时,它应该触发handleChange()
方法。此外, input
元素应该呈现处于组件状态的input
值。 button
元素应在单击时触发submitMessage()
方法。其次,写下这两种方法。该handleChange()
方法应该更新input
与用户正在打字。 submitMessage()
方法应将当前消息(存储在input
)连接到本地状态的messages
数组,并清除input
的值。最后,使用ul
映射messages
数组并将其作为li
元素列表呈现给屏幕。 render()
方法中,让组件渲染input
、button
、ul
三个元素。input
元素的改变会触发handleChange()
方法。此外,input
元素会渲染组件状态中input
的值。点击按钮button
需触发submitMessage()
方法。
+接着,写出这两种方法。handleChange()
方法会更新input
为用户正在输入的内容。submitMessage()
方法把当前存储在input
的消息与本地状态的messages
数组连接起来,并清除input
的值。
+最后,在ul
中展示messages
数组,其中每个元素内容需放到li
元素内。
+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
组件应渲染含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 && 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 (
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));
+ }
+}
+```
+
+addMessage()
的动作创建器。编写函数mapDispatchToProps()
,将dispatch
作为参数,然后返回一个对象。该对象应该将一个属性submitNewMessage
设置为dispatch函数,该函数在调度addMessage()
时为新消息添加一个参数。 addMessage()
。写出接收dispatch
为参数的函数mapDispatchToProps()
,返回一个 dispatch 函数对象,其属性为submitNewMessage
。该函数在 dispatch addMessage()
时为新消息提供一个参数。
+addMessage
应该返回一个带有键type
和message
的对象。
+ - text: addMessage
应返回含type
和message
两个键的对象。
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: 使用submitNewMessage
的mapDispatchToProps
调度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
Provider
组件允许您为React组件提供state
和dispatch
,但您必须准确指定所需的状态和操作。这样,您可以确保每个组件只能访问所需的状态。您可以通过创建两个函数来完成此任务: mapStateToProps()
和mapDispatchToProps()
。在这些函数中,您可以声明要访问的状态段以及需要分配的操作创建者。一旦这些功能到位,您将看到如何使用React Redux connect
方法在另一个挑战中将它们连接到您的组件。 注意:在幕后,React Redux使用store.subscribe()
方法实现mapStateToProps()
。 Provider
可向 React 组件提供state
和dispatch
,但你必须确切地指定所需要的 state 和 actions,以确保每个组件只能访问所需的 state。完成这个任务,你需要创建两个函数:mapStateToProps()
、mapDispatchToProps()
。
+在这两个函数中,声明 state 中函数所要访问的部分及需要 dispatch 的创建 action 的函数。完成这些,我们就可以迎接下一个挑战,学习如何使用 React Redux 的connect
方法来把函数连接到组件了。
+注意: 在幕后,React Redux 用store.subscribe()
方法来实现mapStateToProps()
。
+mapStateToProps()
。此函数应将state
作为参数,然后返回将该状态映射到特定属性名称的对象。您可以通过props
访问这些属性。由于此示例将应用程序的整个状态保存在单个数组中,因此您可以将整个状态传递给组件。在要返回的对象中创建属性messages
,并将其设置为state
。 mapStateToProps()
函数,以state
为参数,然后返回一个对象,该对象把 state 映射到特定属性名上,这些属性能通过props
访问组件。由于此示例把 app 应用的整个状态保存在单一数组中,你可把整个状态传给组件。在返回的对象中创建messages
属性,并设置为state
。
+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
import
语句(这些语句提取了在挑战中为您提供的所有依赖项)。 “使用npm管理包”部分更详细地介绍了npm。最后,编写React和Redux代码通常需要一些配置。这可能很快变得复杂。如果您有兴趣在自己的机器上进行实验,可以配置Create React App并准备就绪。或者,您可以在CodePen中启用Babel作为JavaScript预处理器,将React和ReactDOM添加为外部JavaScript资源,并在那里工作。 import
语句(这些语句引入了各挑战中提供的所有依赖关系),其代码看起来类似。“管理包(含 npm)”这一节更详细地介绍了 npm。
+最后,写 React 和 Redux 的代码通常需要一些配置,且很快会变得复杂起来。如果你想在自己的机器上尝试写代码,点击链接
+创建 React App 可获取已配置好的现成代码。
+另一种做法是在 CodePen 中启用 Babel 作为 JavaScript 预处理器,将 React 和 ReactDOM 添加为外部 JavaScript 资源,在那里编写应用。
+'Now I know React and Redux!'
到控制台。 'Now I know React and Redux!'
这一消息输出到控制台。
+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
react-redux
包来帮助完成这些任务。 React Redux提供了一个小API,它有两个关键特性: Provider
和connect
。另一个挑战包括connect
。 Provider
是React Redux的一个包装组件,它包装了你的React应用程序。然后,此包装器允许您访问整个组件树中的Redux store
和dispatch
功能。 Provider
需要两个道具,Redux商店和应用程序的子组件。为App组件定义Provider
可能如下所示: <Provider store = {store}>
<应用/>
</提供商>
react-redux
包可帮助我们完成这些任务。
+React Redux 提供的 API 有两个关键的功能:Provider
和connect
。你会在另一个挑战中学connect
。Provider
是 React Redux 包装 React 应用的 wrapper 组件,它允许你访问整个组件树中的 Reduxstore
及dispatch(分发)
方法。Provider
需要两个 props:Redux store 和 APP 应用的子组件。用于 APP 组件的Provider
可这样定义:
+
+```jsx
+DisplayMessages
组件。唯一的新部分是底部的AppWrapper
组件。使用此顶级组件从ReactRedux
呈现Provider
,并将Redux存储作为prop传递。然后将DisplayMessages
组件渲染为子级。完成后,您应该看到React组件呈现给页面。 注意: React Redux在此处可用作全局变量,因此您可以使用点表示法访问提供程序。编辑器中的代码利用了这一点并将其设置为一个常量Provider
供您在AppWrapper
渲染方法中使用。 DisplayMessages
组件。新出现的代码是底部的AppWrapper
组件,这个顶级组件可用于渲染ReactRedux
的Provider
,并把 Redux 的 store 作为 props 传入。接着,渲染DisplayMessages
为子组件。完成这些任务后,你会看到 React 组件渲染到页面上。
+注意: React Redux 在此可作全局变量,因此你可通过点号表示法访问 Provider。利用这一点,编辑器上的代码把Provider
设置为常量,便于你在AppWrapper
渲染方法中使用。
+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('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: