redux


Redux介紹

 

單一數據源

整個單頁應用的 state 都被儲存在store的內部,可以通過store.getState()獲取,再作為props傳給對應的組件。

state應該盡量少嵌套扁平化,通過id相互引用數據。

import { createStore } from 'redux';
const store = createStore(reducer);           //stroe需要reducer計算新的state

//createStore函數的簡單實現
const createStore = (reducer) => {
  let state;
  let listeners = [];
  //返回store中的state tree的API
  const getState = () => state;
  //store.dispatch()會自動調用reducer生成新的state,並執行注冊的回調函數
  const dispatch = (action) => {   
    //調用reducer更新state
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  //注冊回調函數,通常是render或setState
  const subscribe = (listener) => {
    listeners.push(listener);
return () => {
    //注銷回調函數
      listeners = listeners.filter(l => l !== listener);
    }
  };

  dispatch({});
  return { getState, dispatch, subscribe };
}; 

State 為只讀

為防止出現race condition和deep equal的遍歷,state應為只讀,不能直接修改state,每個state都是新對象,而改變 state 的方法只有dispatch action。

使用純函數reducer來修改state

reducer 是純函數,接收prestate 和 action,返回新的 state。reducer必須保證同樣的參數輸入其輸出一樣,所以redux規定在Reducer函數里面不能直接改變 State,必須返回新的對象。在reducer中,不能改寫參數,不能調用系統 I/O 的API,不能調用Date.now()或者Math.random()等不純的方法,只是單純的執行計算。例如 API 調用或路由跳轉等有副作用的操作應該在 dispatch action 前發生。

function reducer(state, action) {
   switch (type) {
    case ADD_CHAT:
            //若state 是一個對象,不能直接修改state,而應使用如下方式返回新建的state
      return Object.assign({}, state, { newState });   
          // 或者return  { ...state, ...newState };
    default: return state;      //如果action不屬於該reducer負責,需要返回原state
  }
}

// 若State 是一個數組
function reducer(state, action) {
  return [...state, newItem];
   //或者
   return […state].concat(newItem);
}        

也可以使用immutable.js把 State 對象設成只讀,這樣改變State時只能通過生成一個新對象。

因為reducer函數的作用只是接收一個state計算后返回一個新的state,所以在應用中可以將多個分布在不同目錄的reducer(函數)合成一個rootReducer,每個小的reducer只負責管理全局store中對應的一部分state:

export default function todoAppReducer(state = {}, action) {
  return {
    //只負責store中visibilityFilter和todos字段的state,別的state不可訪問
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todosReducer(state.todos, action)                  //state.todos是大state中todosReducer負責的state
  }
}

//等價於

import { combineReducers } from 'redux';
const todoAppReducer = combineReducers({
  visibilityFilter,                //store的state樹中的節點名和reducer函數名相同
  todos:todosReducer             //      設置不同的對應名字
})
export default todoApp;
//combineReducer將子reducer合成一個大reducer。每個 reducer 根據 key 來篩選出 state 中的一部分數據並處理。最后這個生成的函數再將所有 reducer 的結果合並成一個大的對象,從而更新store。
const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](state[key], action);
        return nextState;
      },
      {}
    );
  };
};

  array.reduce(function(accumulator, currentValue, currentIndex, array), initialValue)

  注意:若無initialValue,reduce 會跳過索引0從索引1開始執行 callback,若提供 initialValue,則從索引0開始。

如果需要在一個 Reducer 中訪問另外一個 Reducer 負責的 state,這時需要自己寫root Reducer或用reduce-reducers來控制。

state可以嵌套,對應的reducer亦可以嵌套,最終由combineReducer方法生成finalReducers,對應的state結構和reducer的嵌套結構相同。

 

action 是一個用於抽象描述已發生事件的普通對象,可以被序列化,是 store 數據的唯一來源。應盡量減少在 action 中傳遞的數據。

action Creator

View要發送多少種消息,就會有多少種 Action。可以定義一個Action Creato函數來生成 Action,不用每次都寫樣板代碼。

const ADD_TODO = '添加 TODO';
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
store.dispatch( addTodo('Learn Redux') );      //store會自動調用reducer得到新的state
store.dispatch( addTodo('Learn React) ); 

可以直接通過 store.dispatch() 調用 dispatch() 方法,也可以用 react-redux 包提供的 connect()方法調用,更方便。

@connect() es7的類修飾器

React-Redux

Redux 和 React 之間沒有直接關系,可以在react中直接使用store的方法,也可以通過react-redux模塊進行綁定。

 

容器組件

展示組件

Location

最頂層,路由處理

中間和子組件,可復用

Aware of Redux

讀取數據

從 Redux 獲取 state

從 props 獲取數據

修改數據

向 Redux 派發 actions

從 props 調用回調函數

 

 

 

 

 

 

 

 

容器組件Container Components:使用redux的API進行數據管理,帶有內部狀態。

展示組件Presentational Components:不使用任何 Redux 的 API,通過props傳遞數據,負責UI呈現,沒有狀態(不使用this.state這個變量)。

connect方法

從 UI 組件生成容器組件(UI component => container container),將react (ui)和redux (store)綁定起來,可以在嵌套的不同層次使用。

/*container component
*容器組件負責管理數據和業務邏輯,UI組件只顯示視圖
*/
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

//mapStateToProps將state映射到UI組件的參數(props),返回一個對象,其屬性代表 UI 組件的同名參數。
//mapStateToProps會訂閱 Store,每當state更新的時候,就會自動執行,最后觸發 UI 組件的重新渲染。如果不傳入mapStateToProps參數,store的更新不會引起UI組件的更新。
const mapStateToProps = (state, ownProps) =>{
  //state指reduce負責的那部分state,ownProps指容器父組件傳入的所有屬性
  //active是傳給子組件的prop屬性,父組件傳入的其他props也會在子組件中
  return{
    active: ownProps.filter === state.visibilityFilter
  }
}

//如果mapDispatchToProps為函數,則應返回一個對象,其每個鍵值對都是一個映射,定義了用戶對 UI 組件的操作怎樣分發出 Action。
//若mapDispatchToProps是一個對象,則屬性名對應UI組件的同名參數,屬性值是一個當作Action creator的函數,其返回的action會由Redux自動發出給store。
const mapDispatchToProps = (dispatch, ownProps) => ({
    //onClick是傳給子組件調用的方法,在view觸發action時調用
  onClick: () => {
    dispatch(setVisibilityFilter(ownProps.filter))
  }
})

//如果不傳入mapStateToProps/mapStateToProps,將默認只傳入dispatch和父組件傳入的props,不傳入store
const FilterLink = connect(     // FilterLink是容器組件
  mapStateToProps,          // 將全局state對象映射到 UI 組件
  mapDispatchToProps        // UI 組件將傳出action對象的方法
)(Link)             //Link為UI組件

export default FilterLink

 

/*UI 組件,只負責UI的呈現*/
import React from 'react'

//active和onClick是父容器組件通過connect方法傳入的props屬性
const Link = ({ active, children, onClick }) => {
  if (active) {
    return <span>{children}</span>
  }
  return (
    <a href="#"
       onClick={e => {
         e.preventDefault()
         onClick()
       }}
    >
      {children}
    </a>
  )
}

export default Link

<Provider> 組件

使用Provider組件包裹根組件,將store傳入容器組件,並通過組件的context屬性使嵌套的所有子組件都能獲得根state而不用一級一級傳遞下去。

//在任意組件引用全局store,盡量不要直接操作store的實例
Link.contextTypes = {
  store: React.PropTypes.object
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM