264 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			264 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
|   | --- | |||
|  | id: 5a24c314108439a4d4036144 | |||
|  | title: 使用 Provider 連接 Redux 和 React | |||
|  | challengeType: 6 | |||
|  | forumTopicId: 301435 | |||
|  | dashedName: use-provider-to-connect-redux-to-react | |||
|  | --- | |||
|  | 
 | |||
|  | # --description--
 | |||
|  | 
 | |||
|  | 在上一挑戰中,創建了 Redux store 和 action,分別用於處理消息數組和添加新消息。 下一步要爲 React 提供訪問 Redux store 及發起更新所需的 actions。 `react-redux` 包可幫助我們完成這些任務。 | |||
|  | 
 | |||
|  | React Redux 提供的 API 有兩個關鍵的功能:`Provider` 和 `connect`。 會在另一個挑戰會介紹 `connect`。 `Provider`是 React Redux 包裝 React 應用的 wrapper 組件, 它允許訪問整個組件樹中的 Redux `store` 及 `dispatch`(分發)方法。 `Provider` 需要兩個 props:Redux store 和 App 應用的子組件。 用於 App 組件的 `Provider` 可這樣定義: | |||
|  | 
 | |||
|  | ```jsx | |||
|  | <Provider store={store}> | |||
|  |   <App/> | |||
|  | </Provider> | |||
|  | ``` | |||
|  | 
 | |||
|  | # --instructions--
 | |||
|  | 
 | |||
|  | 此時,編輯器上顯示的是過去幾個挑戰中所有代碼, 包括 Redux store、actions、`DisplayMessages` 組件。 新出現的代碼是底部的`AppWrapper`組件, 這個頂級組件可用於渲染 `ReactRedux` 的 `Provider`,並把 Redux 的 store 作爲 props 傳入。 接着,渲染 `DisplayMessages` 爲子組件。 完成這些任務後,會看到 React 組件渲染到頁面上。 | |||
|  | 
 | |||
|  | **注意:** React Redux 在此可作全局變量,因此可通過點號表示法訪問 Provider。 利用這一點,編輯器上的代碼把 `Provider` 設置爲常量,便於你在 `AppWrapper` 渲染方法中使用。 | |||
|  | 
 | |||
|  | # --hints--
 | |||
|  | 
 | |||
|  | `AppWrapper` 應渲染。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); | |||
|  |     return mockedComponent.find('AppWrapper').length === 1; | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | `Provider` 組件應傳入相當於 Redux store 的 `store` 參數。 | |||
|  | 
 | |||
|  | ```js | |||
|  | (getUserInput) => | |||
|  |   assert( | |||
|  |     (function () { | |||
|  |       const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); | |||
|  |       return __helpers | |||
|  |         .removeWhiteSpace(getUserInput('index')) | |||
|  |         .includes('<Providerstore={store}>'); | |||
|  |     })() | |||
|  |   ); | |||
|  | ``` | |||
|  | 
 | |||
|  | `DisplayMessages` 應渲染爲 `AppWrapper` 的子組件。 | |||
|  | 
 | |||
|  | ```js | |||
|  | assert( | |||
|  |   (function () { | |||
|  |     const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); | |||
|  |     return ( | |||
|  |       mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1 | |||
|  |     ); | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | `DisplayMessages` 組件應渲染 `h2`、`input`、`button`、`ul` 四個元素。 | |||
|  | 
 | |||
|  | ```js | |||
|  | 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 | |||
|  |     ); | |||
|  |   })() | |||
|  | ); | |||
|  | ``` | |||
|  | 
 | |||
|  | # --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 | |||
|  |   } | |||
|  | }; | |||
|  | 
 | |||
|  | const messageReducer = (state = [], action) => { | |||
|  |   switch (action.type) { | |||
|  |     case ADD: | |||
|  |       return [ | |||
|  |         ...state, | |||
|  |         action.message | |||
|  |       ]; | |||
|  |     default: | |||
|  |       return state; | |||
|  |   } | |||
|  | }; | |||
|  | 
 | |||
|  | 
 | |||
|  | 
 | |||
|  | const store = Redux.createStore(messageReducer); | |||
|  | 
 | |||
|  | // React: | |||
|  | 
 | |||
|  | 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() {   | |||
|  |     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> | |||
|  |     ); | |||
|  |   } | |||
|  | }; | |||
|  | 
 | |||
|  | const Provider = ReactRedux.Provider; | |||
|  | 
 | |||
|  | class AppWrapper extends React.Component { | |||
|  |   // Render the Provider below this line | |||
|  | 
 | |||
|  |   // Change code above this line | |||
|  | }; | |||
|  | ``` | |||
|  | 
 | |||
|  | # --solutions--
 | |||
|  | 
 | |||
|  | ```jsx | |||
|  | // Redux: | |||
|  | 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: | |||
|  | 
 | |||
|  | 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() { | |||
|  |     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> | |||
|  |     ); | |||
|  |   } | |||
|  | }; | |||
|  | 
 | |||
|  | const Provider = ReactRedux.Provider; | |||
|  | 
 | |||
|  | class AppWrapper extends React.Component { | |||
|  |   // Change code below this line | |||
|  |   render() { | |||
|  |     return ( | |||
|  |       <Provider store = {store}> | |||
|  |         <DisplayMessages/> | |||
|  |       </Provider> | |||
|  |     ); | |||
|  |   } | |||
|  |   // Change code above this line | |||
|  | }; | |||
|  | ``` |