在寫Redux的時候我們就了解了 如果使用Redux的話配合React是最好的 Dan Abramov為此還特意封裝了一個react-redux庫來提供便利
一旦我們選擇使用了這個react-redux庫 那么我們的組件概念就要加以區分了 從現在起我們的組件分為展示組件和容器組件兩種 (參考了通俗易懂的阮大神博客)
展示組件(PResentational component) 也叫UI組件、純組件 特點如下:
負責UI顯示無狀態不使用this.state數據來自this.props不使用任何redux的API展示組件其實就是把我們的普通組件的數據與邏輯抽離出來
容器組件(container component) 特點如下:
負責管理數據和業務邏輯帶有內部狀態使用redux的API容器組件是由我們react-redux庫的API通過展示組件生成的
從它們的名字也可以猜到,它們是內外關系 容器組件包裹著展示組件
說的再通俗一些 我們原來是將結構和邏輯都裝在一個組件中 現在將這個組件繼續拆成負責視圖的組件和負責邏輯數據的組件 這樣做有如下優點:
理解 數據與邏輯分開,更便于我們理解分離 必須將標簽拆分,可用性更強重用 一個展示組件可以搭配不同容器組件視圖 展示組件可以放到單獨頁面中調整UI下面我們來看一下react-redux庫的核心 connect()方法與Provider組件
上面也說到了 我么的容器組件是由庫API得到的 而這個函數就是connect connect的意思就是連接展示組件與容器組件的意思 為了加以區分,我用Container表示容器組件,用Component表示展示組件 用法如下
import {connect} from 'react-redux';const Container = connect()(Component);結構就是這個樣子
<Container> <Component/></Container>不過現在我們僅僅是通過展示組件生成了一個容器組件 并且將它們連接了起來 但是容器組件中并沒有數據和邏輯 只是一具空殼,毫無意義 所以我們還需要向這個connect函數中傳入兩個參數 它接收兩個值作為參數:(實際是四個,另外兩個不常用暫時不講)
mapStateToProps(輸入邏輯) 負責將通過state獲得的數據映射到展示組件的this.propsmapDispatchToProps(輸出邏輯) 負責將用戶操作轉化為Action的功能函數映射到展示組件的this.props名字就和reducer一樣,只是官方的概念性叫法(不過還是蠻形象的) 使用的時候可以自定義名字(不過一定要語義化)
所以完整的用法應該是這樣的
const Container = connect( mapStateToProps, mapDispatchToProps)(Component);但是此時mapStateToProps與mapDispatchToProps我們還沒有定義
mapStateToProps負責將state的數據映射到展示組件的this.props 它是一個函數,接收參數state對象 如果有必要的話,還可以使用第二個參數:容器組件的props屬性 返回一個對象表示state到展示組件props的映射關系
const mapStateToProps = (state) => { return { list: state.list }}此時你會發現這個函數名有多合適
返回對象中的“值”——state.list
表示我們要將state的list數組傳遞給內部的展示組件返回對象中的“鍵”—— list
表示我們在展示組件中可以通過this.props.list來獲取這個數組但有時,我們不能這么輕松的就通過state的某個屬性值獲得要傳遞的數據 這時我們可以自定義一個處理函數返回要傳遞的數據
const mapStateToProps = (state) => { return { list: handler(state.list, state.option); }}比如說這里handler就是我們的處理函數 拿我上一篇文章的toDoList待辦事項列表為例 這個handler大概是這樣的
const handler = (list, option) => { switch(option){ case "SHOW_ACTION": return list.filter(...); case "SHOW_CROSSED": return list.filter(...); ... default: return list; }}這個函數我沒有寫完整,相信大家應該都能看明白 通過判斷option我來將list數組進行 “過濾” 函數返回后作為數據返回給展示組件
mapStateToProps會訂閱store,state更新后,就會觸發展示組件重繪 不過在connect( )函數中,我們可以省略mapStateToProps 如果這么做的話,store更新就不會觸發展示組件重繪了
上面也說道了,除了state我們還可以使用容器組件的屬性props
const mapStateToProps = (state, ownProps) => { return { ... }}如果容器組件的props發生改變的話,同樣會觸發展示組件重繪
mapDispatchToProps負責定義發送action的函數映射到展示組件的this.props 與它的兄弟不同,它既可以是函數也可以是對象 作為函數,它會得到store.dispatch作為參數 同樣還有一個容器組件的props屬性可以使用 返回值我不用說大家也能猜到 就是一個表示映射關系的對象 但是這里表示的是用戶如何發出Action(比如觸發事件)
const mapDispatchToProps = (state, ownProps) => { return { onClick: () => { dispatch({ type: 'SET_FILTER', filter: ownProps.filter }) } }}返回對象中的“值”——() => {dispatch(...)}
表示我們要傳遞給內部展示組件的函數(函數功能:dispatch一個action)返回對象中的“鍵”—— onClick
表示我們在展示組件中可以通過this.props.onClick來獲取這個函數如果是作為對象的話,就更簡單了 上面的寫法和下面的等價
const mapDispatchToProps = { onClick: (filter) => { type: 'SET_FILTER', filter: filter }}這個對象的值是一個函數,它被認為是一個Action Creator 函數的參數可以填入容器組件的props 返回的Action會由redux自動dispatch
在完成了Container與Componet的連接 實現了Container的管理數據與業務邏輯之后還沒完 還有問題 我們使用了mapStateToProps,它的參數是state 也就是說,他需要傳入state 如果我們手動將state對象一層一層的傳入容器組件 應用小還好說,大應用深層的組件簡直累死了,絕對讓你傳到懷疑人生
好在,react-redux提供了Provider組件讓我們省了不少功夫 它就相當于我們整體的容器組件(不過區別很大) 用法就是在我們根組件外部嵌套一層Provider,傳入store (使用全局的store有風險) 這樣所以的子組件都可以開心地拿到state了 我們也省心了
render( <Provider store={store}> <App/> </Provider>, document.getElementById('root'));內部的原理是: Provider接受store作為其props,并聲明為context的屬性之一 子組件在聲明了contextTypes之后可以通過this.context.store訪問到store
上一次介紹Redux的時候介紹了一個簡單的計數器 這次我把那個代碼拿過來改裝一下
import React from 'react';import {Component} from 'react'import ReactDom from 'react-dom';import {createStore, combineReducers} from 'redux';import {connect, Provider} from 'react-redux';首先定義單純用來展示UI的展示組件
class Counter extends Component { render(){ const {value, reduceHandler, addHandler} = this.props; return ( <div> <p>{value}</p> <button onClick={reduceHandler}>-</button> <button onClick={addHandler}>+</button> </div> ) }};然后定義映射函數,生成容器組件
const mapStateToProps = (state) => { return { value: state.cnt }}const mapDispatchToProps = (dispatch) => { return { reduceHandler: () => { dispatch({type: 'REDUCE'}); }, addHandler: () => { dispatch({type: 'ADD'}); } }}const APP = connect(mapStateToProps, mapDispatchToProps)(Counter);Reducer稍微修改一下
const reducer = (state = {cnt: 0}, action) => { switch (action.type) { case 'ADD': return {cnt: state.cnt + 1}; case 'REDUCE': return {cnt: state.cnt - 1}; default: return state; }};const store = createStore(reducer);渲染函數中的結構外部嵌套Provider并添加store
ReactDom.render( <Provider store={store}> <APP/> </Provider>, document.getElementById('root'));有了Provider,我們也就不需要 store.dispatch()
了 它會幫我們處理渲染 最后的樣式依然是那個樣子
==主頁傳送門==
新聞熱點
疑難解答