react-redux 進階

klven發表於2018-04-09

Action

Action 是把資料從應用(伺服器響應,使用者輸入或其它非 view 的資料 )傳到 store 的有效載荷。它是 store 資料的唯一來源。一般來說你會通過 store.dispatch() 將 action 傳到 store。分下邊兩類.


/*
 * action 常量
 */
export const ADD_TODO = 'ADD_TODO';
export const VisibilityFilters = {
    SHOW_ALL: 'SHOW_ALL',
    SHOW_COMPLETED: 'SHOW_COMPLETED',
}

/*
 * action 建立函式
 */
export function addTodo(text) {
    return { type: ADD_TODO, text }
}

export const addTodo = (id)=>{
    return {
        type: EDITORUSERID,
        id:id
    }
}
複製程式碼

reducer

是一個純函式,接收舊的 state 和 action,返回新的 state。 (previousState, action) => newState

注意:永遠不要在 reducer 裡做這些操作:

  • 修改傳入引數;
  • 執行有副作用的操作,如 API 請求和路由跳轉;
  • 呼叫非純函式,如 Date.now() 或 Math.random()。
function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
          default:
      return state
  }
}
複製程式碼

combineReducers管理多個Reducer

const todoApp = combineReducers({
  visibilityFilter,
  todos
})
export default todoApp

當你觸發 action 後,combineReducers 返回的 todoApp 會負責呼叫兩個 reducer:
 let nextTodos = todos(state.todos, action)


注意:也可以 reducer 放到一個獨立的檔案中,通過 export 暴露出每個 reducer 函式import * as reducers from './reducers'


複製程式碼

Store

Store 有以下職責:

  • 維持應用的 state;
  • 提供 getState() 方法獲取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通過 subscribe(listener) 註冊監聽器;
  • 通過 subscribe(listener) 返回的函式登出監聽器
import todoApp from './reducers'
let store = createStore(todoApp)
複製程式碼

createStore() 的第二個引數是可選的, 用於設定 state 初始狀態。這對開發同構應用時非常有用,伺服器端 redux 應用的 state 結構可以與客戶端保持一致, 那麼客戶端可以將從網路接收到的服務端 state 直接用於本地資料初始化。

let store = createStore(todoApp, window.STATE_FROM_SERVER)

容器元件

Redux 的 React 繫結庫是基於 容器元件和zhan shi元件相分離 的開發思想

展示元件 容器元件
作用 描述如何展現(骨架、樣式) 描述如何執行(資料獲取、狀態更新)
直接使用 Redux
資料來源 props 監聽 Redux state
資料修改 從 props 呼叫回撥函式 向 Redux 派發 actions
呼叫方式 手動 通常由 React Redux 生成

展示元件就是一般的js檔案容器元件往往使用connect(mapStateToProps,mapDispatchToProps) 建立。 mapStateToProps是把容器元件state向展示元件props對映。mapDispatchToProps() 是對映回撥方法。例如,我們希望 VisibleTodoList 向 TodoList 元件中注入一個叫 mOnClick 的 props ,還希望 onTodoClick 能分發 increaseAction 這個 action:

const App=connect(
    (state)=>({
        value:state.count
    }),(dispatch)=>({
        mOnClick:()=>dispatch(increaseAction)
    })
)(Counter);
複製程式碼

Provider

所有容器元件都可以訪問 Redux store,建議的方式是使用指定的 React Redux 元件 來包裹,讓所有容器元件都可以訪問 store,

let store = createStore(todoApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

複製程式碼
//建立元件的簡單寫法
const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

export default App
複製程式碼

高階部分(處理非同步Action)

標準的做法是使用 Redux Thunk 中介軟體。 action 建立函式除了返回 action 物件外還可以返回函式。這時,這個 action 建立函式就成為了 thunk。這個函式會被 Redux Thunk middleware 執行。

我們仍可以在 actions.js 裡定義這些特殊的 thunk action 建立函式。

建立thunk action

//thunk action 
// 雖然內部操作不同,你可以像其它 action 建立函式 一樣使用它:
// store.dispatch(fetchPosts('reactjs'))

export function fetchPosts(subreddit) {
  return function (dispatch) {

    // 首次 dispatch:更新應用的 state 來通知
    // API 請求發起了。
    dispatch(requestPosts(subreddit))
    
    return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
      .then(
        response => response.json(),
         error => console.log('An error occurred.', error)
      )
      .then(json =>
        dispatch(receivePosts(subreddit, json))
      )
  }
}

export function fetchPostsIfNeeded(subreddit) {
  // 當快取的值是可用時,
  // 減少網路請求很有用。

  return (dispatch, getState) => {
    if (shouldFetchPosts(getState(), subreddit)) {
      // 在 thunk 裡 dispatch 另一個 thunk!
      return dispatch(fetchPosts(subreddit))
    } else {
      // 告訴呼叫程式碼不需要再等待。
      return Promise.resolve()
    }
  }
}

複製程式碼
middleware

你可以利用 Redux middleware 來進行日誌記錄、建立崩潰報告、呼叫非同步介面或者路由等等。應用中介軟體要改造下createStore()

 * 記錄所有被髮起的 action 以及產生的新的 state。
 */
const logger = store => next => action => {
  console.group(action.type)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

let store = createStore(
  todoApp,
  applyMiddleware(
    logger
  )

複製程式碼

優化減少模版程式碼

1 action優化 1.1 你可以寫一個用於生成 action creator 的函式:

function makeActionCreator(type, ...argNames) {
  return function(...args) {
    let action = { type }
    argNames.forEach((arg, index) => {
      action[argNames[index]] = args[index]
    })
    return action
  }
}

const ADD_TODO = 'ADD_TODO'
const EDIT_TODO = 'EDIT_TODO'

export const addTodo = makeActionCreator(ADD_TODO, 'todo')
export const editTodo = makeActionCreator(EDIT_TODO, 'id', 'todo')

複製程式碼

1.2非同步 Action Creators

export function loadPosts(userId) {
  return {
    // 要在之前和之後傳送的 action types
    types: ['LOAD_POSTS_REQUEST', 'LOAD_POSTS_SUCCESS', 'LOAD_POSTS_FAILURE'],
    // 檢查快取 (可選):
    shouldCallAPI: (state) => !state.users[userId],
    // 進行取:
    callAPI: () => fetch(`http://myapi.com/users/${userId}/posts`),
    // 在 actions 的開始和結束注入的引數
    payload: { userId }
  };
}
複製程式碼

2.reducer重構

方法抽取

function addTodo(state, action) {
    ...
    return updateObject(state, {todos : newTodos});
}
function todoReducer(state = initialState, action) {
    switch(action.type) {
        case 'SET_VISIBILITY_FILTER' : return setVisibilityFilter(state, action);
        case 'ADD_TODO' : return addTodo(state, action);
       
        default : return state;
    }
}
複製程式碼

善用combineReducers函式

// 使用 ES6 的物件字面量簡寫方式定義物件結構
const rootReducer = combineReducers({
    todoReducer,
    firstNamedReducer
});

const store = createStore(rootReducer);
複製程式碼

3.大多數應用會處理多種資料型別,通常可以分為以下三類:

  • 域資料(Domain data): 應用需要展示、使用或者修改的資料(比如 從伺服器檢索到的所有 todos
  • 應用狀態(App state): 特定於應用某個行為的資料(比如 “Todo #5 是現在選擇的狀態”,或者 “正在進行一個獲取 Todos 的請求”)
  • UI 狀態(UI state): 控制 UI 如何展示的資料(比如 “編寫 TODO 模型的彈窗現在是展開的”)

一個典型的應用 state 大致會長這樣:

{
    domainData1 : {},
    domainData2 : {},
    appState1 : {},
    appState2 : {},
    ui : {
        uiState1 : {},
        uiState2 : {},
    }
}
複製程式碼

必要時可採用 Redux-ORM

參考

github redux Redux 中文文件

如有疏漏,請指出,如有問題可以通過如下方式聯絡我

簡書 csdn 掘金 klvens跑碼場