| 
									
										
										
										
											2021-05-05 10:13:49 -07:00
										 |  |  |  | --- | 
					
						
							|  |  |  |  | id: 5a24c314108439a4d4036148 | 
					
						
							|  |  |  |  | title: 將 Redux 連接到 Messages App | 
					
						
							|  |  |  |  | challengeType: 6 | 
					
						
							|  |  |  |  | forumTopicId: 301427 | 
					
						
							|  |  |  |  | dashedName: connect-redux-to-the-messages-app | 
					
						
							|  |  |  |  | --- | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # --description--
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 知道如何使用 `connect` 連接 React 和 Redux 後,我們可以在 React 組件中應用上面學到的內容。 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 在上一課,連接到 Redux 的組件命名爲 `Presentational`,這個命名不是任意的, 這樣的術語*通常*是指未直接連接到 Redux 的 React 組件, 它們只負責執行接收 props 的函數來實現 UI 的呈現。 相比之下,容器組件用來連接到 Redux 上。 這些組件通常負責把 actions 分派給 store,且經常給子組件傳入 store state 屬性。 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # --instructions--
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 到目前爲止,我們的編輯器上已包含了整個章節的代碼, 唯一不同的是,React 組件被重新命名爲 `Presentational`,即展示層組件。 創建一個新組件,保存在名爲 `Container` 的常量中。 這個常量用 `connect` 把 `Presentational` 組件和 Redux 連接起來。 然後,在`AppWrapper` 中渲染 React Redux 的 `Provider`組件, 給 `Provider` 傳入 Redux `store` 屬性並渲染 `Container` 爲子組件。 設置完所有內容後,將再次看到消息應用程序渲染到頁面上。 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # --hints--
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-18 11:22:53 -07:00
										 |  |  |  | `AppWrapper` 應該渲染該到頁面上。 | 
					
						
							| 
									
										
										
										
											2021-05-05 10:13:49 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | ```js | 
					
						
							|  |  |  |  | assert( | 
					
						
							|  |  |  |  |   (function () { | 
					
						
							|  |  |  |  |     const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); | 
					
						
							|  |  |  |  |     return mockedComponent.find('AppWrapper').length === 1; | 
					
						
							|  |  |  |  |   })() | 
					
						
							|  |  |  |  | ); | 
					
						
							|  |  |  |  | ``` | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-18 11:22:53 -07:00
										 |  |  |  | `Presentational` 應該渲染到頁面上. | 
					
						
							| 
									
										
										
										
											2021-05-05 10:13:49 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | ```js | 
					
						
							|  |  |  |  | assert( | 
					
						
							|  |  |  |  |   (function () { | 
					
						
							|  |  |  |  |     const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); | 
					
						
							|  |  |  |  |     return mockedComponent.find('Presentational').length === 1; | 
					
						
							|  |  |  |  |   })() | 
					
						
							|  |  |  |  | ); | 
					
						
							|  |  |  |  | ``` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | `Presentational` 組件應渲染 `h2`、`input`、`button`、`ul` 四個元素。 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ```js | 
					
						
							|  |  |  |  | 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 | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   })() | 
					
						
							|  |  |  |  | ); | 
					
						
							|  |  |  |  | ``` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | `Presentational` 組件應接收 Redux store 的 `messages` 屬性。 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ```js | 
					
						
							|  |  |  |  | assert( | 
					
						
							|  |  |  |  |   (function () { | 
					
						
							|  |  |  |  |     const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); | 
					
						
							|  |  |  |  |     const PresentationalComponent = mockedComponent.find('Presentational'); | 
					
						
							|  |  |  |  |     const props = PresentationalComponent.props(); | 
					
						
							|  |  |  |  |     return Array.isArray(props.messages); | 
					
						
							|  |  |  |  |   })() | 
					
						
							|  |  |  |  | ); | 
					
						
							|  |  |  |  | ``` | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-18 11:22:53 -07:00
										 |  |  |  | `Presentational` 組件應接收創建 action 的函數的 `submitMessage` 屬性。 | 
					
						
							| 
									
										
										
										
											2021-05-05 10:13:49 -07:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | ```js | 
					
						
							|  |  |  |  | assert( | 
					
						
							|  |  |  |  |   (function () { | 
					
						
							|  |  |  |  |     const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); | 
					
						
							|  |  |  |  |     const PresentationalComponent = mockedComponent.find('Presentational'); | 
					
						
							|  |  |  |  |     const props = PresentationalComponent.props(); | 
					
						
							|  |  |  |  |     return typeof props.submitNewMessage === 'function'; | 
					
						
							|  |  |  |  |   })() | 
					
						
							|  |  |  |  | ); | 
					
						
							|  |  |  |  | ``` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # --seed--
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ## --after-user-code--
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ```jsx | 
					
						
							|  |  |  |  | ReactDOM.render(<AppWrapper />, document.getElementById('root')) | 
					
						
							|  |  |  |  | ``` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ## --seed-contents--
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ```jsx | 
					
						
							|  |  |  |  | // 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() { | 
					
						
							|  |  |  |  |     this.setState((state) => { | 
					
						
							|  |  |  |  |       const currentMessage = state.input; | 
					
						
							|  |  |  |  |       return { | 
					
						
							|  |  |  |  |         input: '', | 
					
						
							|  |  |  |  |         messages: 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; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // Define the Container component here: | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | class AppWrapper extends React.Component { | 
					
						
							|  |  |  |  |   constructor(props) { | 
					
						
							|  |  |  |  |     super(props); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   render() { | 
					
						
							|  |  |  |  |     // Complete the return statement: | 
					
						
							|  |  |  |  |     return (null); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | }; | 
					
						
							|  |  |  |  | ``` | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | # --solutions--
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | ```jsx | 
					
						
							|  |  |  |  | // 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() { | 
					
						
							|  |  |  |  |     this.setState((state) => { | 
					
						
							|  |  |  |  |       const currentMessage = state.input; | 
					
						
							|  |  |  |  |       return { | 
					
						
							|  |  |  |  |         input: '', | 
					
						
							|  |  |  |  |         messages: 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; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | const Container = connect(mapStateToProps, mapDispatchToProps)(Presentational); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | class AppWrapper extends React.Component { | 
					
						
							|  |  |  |  |   constructor(props) { | 
					
						
							|  |  |  |  |     super(props); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   render() { | 
					
						
							|  |  |  |  |     return ( | 
					
						
							|  |  |  |  |       <Provider store={store}> | 
					
						
							|  |  |  |  |         <Container/> | 
					
						
							|  |  |  |  |       </Provider> | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | }; | 
					
						
							|  |  |  |  | ``` |