精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Redux核心概念

移動開發(fā)
http://gaearon.github.io/redux/index.html ,文檔在http://rackt.github.io/redux/index.html 。本文不是官方文檔的翻譯。你可以在閱讀官方文檔之前和之后閱讀本文,以加深其中的重點(diǎn)概念。根據(jù)該項(xiàng)目源碼的習(xí)慣,示例都是基于 ES2015 的語法來寫的。

[[145606]]

http://gaearon.github.io/redux/index.html ,文檔在http://rackt.github.io/redux/index.html 。本文不是官方文檔的翻譯。你可以在閱讀官方文檔之前和之后閱讀本文,以加深其中的重點(diǎn)概念。

根據(jù)該項(xiàng)目源碼的習(xí)慣,示例都是基于 ES2015 的語法來寫的。

Redux 是應(yīng)用狀態(tài)管理服務(wù)。雖然本身受到了 Flux 很深的影響,但是其核心概念卻非常簡單,就是 Map/Reduce 中的 Reduce。

我們看一下 Javascript 中 Array.prototype.reduce 的用法:

  1. const initState = ''
  2. const actions = ['a''b''c']; 
  3. const newState = actions.reduce( 
  4.     ( (prevState, action) => prevState + action ), 
  5.     initState 
  6. ); 

從 Redux 的角度來看,應(yīng)用程序的狀態(tài)類似于上面函數(shù)中的 initState 和 newState 。給定 initState 之后,隨著 action 的值不斷傳入給計(jì)算函數(shù),得到新的 newState。

這個計(jì)算函數(shù)被稱之為 Reducer,就是上例中的 (prevState, action) => prevState + action。

Immutable State

Redux 認(rèn)為,一個應(yīng)用程序中,所有應(yīng)用模塊之間需要共享訪問的數(shù)據(jù),都應(yīng)該放在 State 對象中。這個應(yīng)用模塊可能是指 React Components,也可能是你自己訪問 AJAX API 的代理模塊,具體是什么并沒有一定的限制。State 以 “樹形” 的方式保存應(yīng)用程序的不同部分的數(shù)據(jù)。這些數(shù)據(jù)可能來自于網(wǎng)絡(luò)調(diào)用、本地?cái)?shù)據(jù)庫查詢、甚至包括當(dāng)前某個 UI 組件的臨時(shí)執(zhí)行狀態(tài)(只要是需要被不同模塊訪問)、甚至當(dāng)前窗口大小等。

Redux 沒有規(guī)定用什么方式來保存State,可能是 Javascript 對象,或者是Immutable.js 的數(shù)據(jù)結(jié)構(gòu)。但是有一點(diǎn),你最好確保 State 中每個節(jié)點(diǎn)都是 Immutable 的,這樣將確保 State 的消費(fèi)者在判斷數(shù)據(jù)是否變化時(shí),只要簡單地進(jìn)行引用比較即可,例如:

  1. newState.todos === prevState.todos 

從而避免 Deep Equal 的遍歷過程。

為了確保這一點(diǎn),在你的 Reducer 中更新 State 成員需要這樣做:

  1. `let myStuff = [ 
  2.     {name: 'henrik'
  3.  
  4. myStuff = [...mystuff, {name: 'js lovin fool']` 

myStuff 是一個全新的對象。

如果更新的是 Object ,則:

  1. let counters = { 
  2.     faves: 0
  3.     forward: 20
  4. // this creates a brand new copy overwriting just that key 
  5. counters = {...counters, faves: counters.faves + 1

而不是:

  1. counters.faves = counters.faves + 1

要避免對 Object 的 in-place editing。數(shù)組也是一樣:

  1. let todos = [ 
  2.     { id: 1, text: 'have lunch'
  3. todos = [...todos, { id: 2, text: 'buy a cup of coffee'} ] 

而不是:

  1. let todos = [ 
  2.     { id: 1, text: 'have lunch'
  3. todos.push({ id: 2, text: 'buy a cup of coffee'}); 

遵循這樣的方式,無需 Immutable.js 你也可以讓自己的應(yīng)用程序狀態(tài)是 Immutable 的。

在 Redux 中,State 只能通過 action 來變更。Reducer 就是根據(jù) action 的語義來完成 State 變更的函數(shù)。Reducer 的執(zhí)行是同步的。在給定 initState 以及一系列的 actions,無論在什么時(shí)間,重復(fù)執(zhí)行多少次 Reducer,都應(yīng)該得到相同的 newState。這使得你的應(yīng)用程序的狀態(tài)是可以被 Log 以及 Replay 的。這種確定性,大大降低了前端開發(fā)所面臨的復(fù)雜狀態(tài)的亂入問題。確定的狀態(tài)、再加上 Hot-Reloaidng 和相應(yīng)的 Dev-Tool,使得前端應(yīng)用的可控性大大增強(qiáng)了。

State 結(jié)構(gòu)設(shè)計(jì)

Redux (Flux) 都建議在保存 State 數(shù)據(jù)的時(shí)候,應(yīng)該盡可能地遵循范式,避免嵌套數(shù)據(jù)結(jié)構(gòu)。如果出現(xiàn)了嵌套的對象,那么盡量通過 ID 來引用。

假設(shè)遠(yuǎn)程服務(wù)返回的數(shù)據(jù)是這樣的:

  1. [{ 
  2.   id: 1
  3.   title: 'Some Article'
  4.   author: { 
  5.     id: 1
  6.     name: 'Dan' 
  7.   } 
  8. }, { 
  9.   id: 2
  10.   title: 'Other Article'
  11.   author: { 
  12.     id: 1
  13.     name: 'Dan' 
  14.   } 
  15. }] 

那么,轉(zhuǎn)換成以下形式會更有效率:

  1.   result: [12], 
  2.   entities: { 
  3.     articles: { 
  4.       1: { 
  5.         id: 1
  6.         title: 'Some Article'
  7.         author: 1 
  8.       }, 
  9.       2: { 
  10.         id: 2
  11.         title: 'Other Article'
  12.         author: 1 
  13.       } 
  14.     }, 
  15.     users: { 
  16.       1: { 
  17.         id: 1
  18.         name: 'Dan' 
  19.       } 
  20.     } 
  21.   } 

范式化的存儲讓你的數(shù)據(jù)的一致性更好,上例中,如果更新了users[1].name,那么在顯示 articles 的 component 中,作者姓名也被更新了。

其實(shí)傳統(tǒng)關(guān)系數(shù)據(jù)庫的設(shè)計(jì)原則就是如此,只不過隨著對數(shù)據(jù)分布能力和水平擴(kuò)展性的要求(放棄了一定程度的數(shù)據(jù)一致性),服務(wù)端數(shù)據(jù)的冗余越來越多。但是回到客戶端,由于需要保存的數(shù)據(jù)總量不大(往往就是用戶最近訪問數(shù)據(jù)的緩存),也沒有分布式的要求,因此范式化的數(shù)據(jù)存儲就更有優(yōu)勢了。除了可以收獲一致性,還可以減少存儲空間(存儲空間在客戶端更加寶貴)。

除此之外,范式化的存儲也利于后面講到的 Reducer 局部化,便于講大的 Reducer 分割為一系列小的 Reducers。

由于服務(wù)器端返回的 JSON 數(shù)據(jù)(現(xiàn)在常見的方式)往往是冗余而非范式的,因此,可能需要一些工具來幫助你轉(zhuǎn)換,例如:https://github.com/gaearon/normalizr , 雖然很多時(shí)候自己控制會更有效一些。

Reducer

下面我們以熟悉 todoApp 來看一下 Reducer 的工作方式:

  1. function todoAppReducer(state = initialState, action) { 
  2.   switch (action.type) { 
  3.   case SET_VISIBILITY_FILTER: 
  4.     return Object.assign({}, state, { 
  5.       visibilityFilter: action.filter 
  6.     }); 
  7.   case ADD_TODO: 
  8.     return Object.assign({}, state, { 
  9.       todos: [...state.todos, { 
  10.         text: action.text, 
  11.         completed: false 
  12.       }] 
  13.     });  
  14.   default
  15.     return state; 
  16.   } 

這個例子演示了 Reducers 是如何根據(jù)傳入的 action.type 分別更新不同的 State 字段。

如果當(dāng)應(yīng)用程序中存在很多 action.type 的時(shí)候,通過一個 Reducer 和巨型 switch 顯然會產(chǎn)生難以維護(hù)的代碼。此時(shí),比較好的方法就是通過組合小的 Reducer 來產(chǎn)生大的 Reducer,而每個小 Reducer 只負(fù)責(zé)處理 State 的一部分字段。如下例:

  1. import { combineReducers } from 'redux'
  2.  
  3. const todoAppReducer = combineReducers({ 
  4.   visibilityFilter: visibilityFilterReducer 
  5.   todos: todosReducer 
  6. }); 

visibilityFilterReducer 和 todosReducer 是兩個小 Reducers,其中一個如下:

  1. function visibilityFilterReducer(state = SHOW_ALL, action) { 
  2.   switch (action.type) { 
  3.   case SET_VISIBILITY_FILTER: 
  4.     return action.filter; 
  5.   default
  6.     return state; 
  7.   } 

visibilityFilterReducer 僅僅負(fù)責(zé)處理 State.visibilityFilter 字段的狀態(tài),這是通過向combineReducers 傳遞如下形式的參數(shù)實(shí)現(xiàn)的:

  1.   field1: reducerForField1, 
  2.   field2: reducerForField2 

filed1 和 filed2 表示 State 中的字段,reducerForField1 和 reducerForField2 是對應(yīng)的 Reducers,每個 Reducers 將僅僅獲得 State.field1 或者 state.field2 的值,而看不到 State 下的其他字段的內(nèi)容。響應(yīng)的返回結(jié)果也會被合并到對應(yīng)的 State 字段中。

使用 combineReducers 的前提是,每一個被組合的 Reducer 僅僅和 State 的一部分?jǐn)?shù)據(jù)相關(guān),例如:todos Reducer 只消費(fèi) state.todos 數(shù)據(jù),也只產(chǎn)生 state.todos 數(shù)據(jù)。如果需要消費(fèi)其他 State 字段,那么還是需要在大 switch 中為特定處理函數(shù)傳入整個 State,例如:

  1. function todoAppReducer(state = initialState, action) { 
  2.   switch (action.type) { 
  3.   case FAVE_ALL_ITEMS: 
  4.     return Object.assign({}, state, faveThemAll(state)); 
  5.   default
  6.     return state; 
  7.   } 

一個 Reducer 可以處理多種 action.type,而 一種 action.type 也可能被多個 Reducers處理,這是多對多的關(guān)系。以下 Helper 函數(shù)可以簡化 Reducer 的創(chuàng)建過程:

  1. function createReducer(initialState, handlers) { 
  2.   return function reducer(state = initialState, action) { 
  3.     if (handlers.hasOwnProperty(action.type)) { 
  4.       return handlers[action.type](state, action); 
  5.     } else { 
  6.       return state; 
  7.     } 
  8.   } 
  9.  
  10. export const todosReducer = createReducer([], { 
  11.   [ActionTypes.ADD_TODO](state, action) { 
  12.     let text = action.text.trim(); 
  13.     return [...state, text]; 
  14.   } 

Store

在 Redux 中,Store 對象就是用來維護(hù)應(yīng)用程序狀態(tài)的對象。構(gòu)造 Store 對象,僅需要提供一個 Reducer 函數(shù)即可。如前所述,這個 Reducer 函數(shù)是負(fù)責(zé)將負(fù)責(zé)解釋 Action 對象的語義,從而改變其內(nèi)部狀態(tài)(也就是應(yīng)用程序的狀態(tài))。

因此 Store 對象有兩個主要方法,一個次要方法:

  1. store.getState(): 獲取最近的內(nèi)部狀態(tài)對象。
  2. store.dispatch(action): 將一個 action 對象發(fā)送給 reducer。

一個次要方法為:const unsure = store.subscribe(listener),用來訂閱狀態(tài)的變化。在 React + Redux 的程序中,并不推薦使用 store.subscribe 。但是如果你的應(yīng)用程序是基于 Observable 模式的,則可以用這個方法來進(jìn)行適配;例如,你可以通過這個方法將 Redux 和你的 FRP (Functional Reactive Programming) 應(yīng)用結(jié)合。

下面這個例子演示了 Store 是如何建立的:

  1. import { combineReducers, createStore } from 'redux'
  2. import * as reducers from './reducers'
  3.  
  4. const todoAppReducer = combineReducers(reducers); 
  5. const store = createStore(todoAppReducer);  // Line 5 
  6.  
  7. store.dispatch({type: 'ADD_TODO', text: 'Build Redux app'}); 

我們也可以在 createStore 的時(shí)候?yàn)?nbsp;Store 指定一個初始狀態(tài),例如替換第 5 行為:

  1. const store = createStore(reducers, window.STATE_FROM_SERVER); 

這個例子中,初始狀態(tài)來自于保存在瀏覽器 window 對象的 STATE_FROM_SERVER 屬性。這個屬性可不是瀏覽器內(nèi)置屬性,是我們的 Web Server 在返回的頁面文件中以內(nèi)聯(lián) JavaScript 方式嵌入的。這是一種 Universal(Isomorphic) Application 的實(shí)現(xiàn)方式。Client 無需發(fā)起第一個 AJAX API 請求,就可以直接從當(dāng)前頁面中直接獲得初始狀態(tài)。

Action

在 Redux 中,改變 State 只能通過 actions。并且,每一個 action 都必須是 Javascript Plain Object,例如:

  1.   type: 'ADD_TODO'
  2.   text: 'Build Redux app' 

Redux 要求 action 是可以被序列化的,使這得應(yīng)用程序的狀態(tài)保存、回放、Undo 之類的功能可以被實(shí)現(xiàn)。因此,action 中不能包含諸如函數(shù)調(diào)用這樣的不可序列化字段。

action 的格式是有建議規(guī)范的,可以包含以下字段:

  1.   type: 'ADD_TODO'
  2.   payload: { 
  3.     text: 'Do something.'   
  4.   }, 
  5.   `meta: {}` 

如果 action 用來表示出錯的情況,則可能為:

  1.   type: 'ADD_TODO'
  2.   payload: new Error(), 
  3.   error: true 

type 是必須要有的屬性,其他都是可選的。完整建議請參考 Flux Standard Action(FSA) 定義。已經(jīng)有不少第三方模塊是基于 FSA 的約定來開發(fā)了。

Action Creator

事實(shí)上,創(chuàng)建 action 對象很少用這種每次直接聲明對象的方式,更多地是通過一個創(chuàng)建函數(shù)。這個函數(shù)被稱為Action Creator,例如:

  1. `function addTodo(text) { 
  2.   return { 
  3.     type: ADD_TODO, 
  4.     text 
  5.   }; 
  6. }` 

Action Creator 看起來很簡單,但是如果結(jié)合上 Middleware 就可以變得非常靈活。

Middleware

如果你用過 Express,那么就會熟悉它的 Middleware 系統(tǒng)。在 HTTP Request 到 Response 處理過程中,一系列的 Express Middlewares 起著不同的作用,有的 Middleware 負(fù)責(zé)記錄 Log,有的負(fù)責(zé)轉(zhuǎn)換內(nèi)部異常為特定的 HTTP Status 返回值,有的負(fù)責(zé)將 Query String 轉(zhuǎn)變到 request 對象的特定屬性。

Redux Middleware 的設(shè)計(jì)動機(jī)確實(shí)是來自于 Express 。其主要機(jī)制為,建立一個 store.dispatch 的鏈條,每個 middleware 是鏈條中的一個環(huán)節(jié),傳入的 action 對象逐步處理,直到最后吐出來是 Javascript Plain Object。先來看一個例子:

  1. import { createStore, combineReducers, applyMiddleware } from 'redux'
  2.  
  3. // applyMiddleware takes createStore() and returns// a function with a compatible API. 
  4. let createStoreWithMiddleware = applyMiddleware( 
  5.   logger, 
  6.   crashReporter 
  7. )(createStore); 
  8.  
  9. // Use it like you would use createStore()let todoApp = combineReducers(reducers); 
  10. let store = createStoreWithMiddleware(todoApp); 

這個例子中,logger 和 crashReporter 這兩個 Middlewares 分別完成記錄 action 日志和記錄 action 處理異常的功能。

logger 的代碼如下:

  1. `// Logs all actions and states after they are dispatched. 
  2. const logger = store => next => action => { 
  3.   console.log('dispatching', action); 
  4.   let result = next(action); 
  5.   console.log('next state', store.getState()); 
  6.   return result; 
  7. };` 

longer 是一個 currying (這是函數(shù)式編程的一個基本概念,相比 Flux,Redux 大量使用了函數(shù)式編程的方式)之后的函數(shù)。next 則是下一個 Middleware 返回的 dispatch 函數(shù)(后面會有分析)。對于一個 Middleware 來說,有了 store對象,就可以通過store.getState() 來獲取最近的應(yīng)用狀態(tài)以供決策,有了 next ,則可以控制傳遞的流程。

ES6 的 Fat Arrow Function 語法(logger = store => next => action =>)讓原本 function 返回 function 的語法變得更簡潔(I ❤️☕️!)。

工業(yè)化的 logger 實(shí)現(xiàn)可以參見:https://github.com/fcomb/redux-logger 和https://github.com/fcomb/redux-diff-logger 。同一個作者寫了兩個,后面這個支持 State 的差異顯示。

vanilla promise

Middleware 還可以用來對傳入的 action 進(jìn)行轉(zhuǎn)換,下面這個例子里,傳入的 action是一個 Promise(顯然不符合 action 必須是 Javascript Plain Object 的要求),因此需要進(jìn)行轉(zhuǎn)換:

  1. `/** 
  2.  * Lets you dispatch promises in addition to actions. 
  3.  * If the promise is resolved, its result will be dispatched as an action. 
  4.  * The promise is returned from `dispatch` so the caller may handle rejection. 
  5.  */ 
  6. const vanillaPromise = store => next => action => { 
  7.   if (typeof action.then !== 'function') { 
  8.     return next(action); 
  9.   } 
  10. `  // the action is a promise` 
  11.   return Promise.resolve(action).then(store.dispatch); 
  12. };` 

這個例子中,如果傳入的 action 是一個 Promise(即包含 .then 函數(shù),這只是一個粗略的判斷),那么就執(zhí)行這個 Promise,當(dāng) Promise 執(zhí)行成功后,將結(jié)果直接傳遞給 store.dispatch(不再經(jīng)過后續(xù)的 Middlewares 鏈)。當(dāng)然,我們要確保 Promise 的執(zhí)行結(jié)果返回的是 Javascript Plain Object。

這種用法可能并非常用,但是從這個例子我們可以體會到,我們可以定義自己 action 的語義,然后通過相應(yīng)的 middleware 進(jìn)行解析,產(chǎn)生特定的執(zhí)行邏輯以生成最終的 action 對象。這個執(zhí)行過程可能是同步的,也可能是異步的。

從這個例子你可能也會發(fā)現(xiàn),如果們也裝載了 logger Middleware,那么 logger 可以知道 Promise action 進(jìn)入了 dispatch 函數(shù)鏈條,但是卻沒有機(jī)會知道最終 Promise 執(zhí)行成功/失敗后發(fā)生的事情,因?yàn)闊o論 Promise 執(zhí)行成功與否,都會直接調(diào)用最原始的 store.dispatch,沒有走 Middlewares 創(chuàng)建的 dispatch 函數(shù)鏈條。

對 Promise 的完整支持請參見:https://github.com/acdlite/redux-promise。

Scheduled Dispatch

下面這個例子略微復(fù)雜一些,演示了如何延遲執(zhí)行一個 action 的 dispatch。

  1. `/** 
  2.  * Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds. 
  3.  * Makes `dispatch` return a function to cancel the interval in this case. 
  4.  */ 
  5. const timeoutScheduler = store => next => action => { 
  6.   if (!action.meta || !action.meta.delay) { 
  7.     return next(action); 
  8.   } 
  9.  
  10.   let intervalId = setTimeout( 
  11.     () => next(action), 
  12.     action.meta.delay 
  13.   ); 
  14.  
  15.   return function cancel() { 
  16.     clearInterval(intervalId); 
  17.   }; 
  18. };` 

這個例子中,timeoutScheduler Middleware 如果發(fā)現(xiàn)傳入的 action 參數(shù)帶有 meta.delay 字段,那么就認(rèn)為這個 action 需要延時(shí)發(fā)送。當(dāng)聲明的延遲時(shí)間(meta.delay)到了,action 對象才會被送往下一個 Middleware 的 dispatch 方法。

下面這個 Middleware 非常簡單,但是卻提供了非常靈活的用法。

Thunk

如果不了解 Thunk 的概念,可以先閱讀http://www.ruanyifeng.com/blog/2015/05/thunk.html 。

thunk Middleware 的實(shí)現(xiàn)非常簡單:

  1. `const thunk = store => next => action => 
  2.   typeof action === 'function' ? 
  3.     action(store.dispatch, store.getState) : 
  4.     next(action);` 

下面的例子裝載了 thunk,且 dispatch 了一個 Thunk 函數(shù)作為 action。

  1. const createStoreWithMiddleware = applyMiddleware( 
  2.   logger, 
  3.   thunk 
  4.   timeoutScheduler 
  5. )(createStore); 
  6. const store = createStoreWithMiddleware(combineReducers(reducers)); 
  7.  
  8. function addFave(tweetId) { 
  9.   return (dispatch, getState) => { 
  10.     if (getState.tweets[tweetId] && getState.tweets[tweetId].faved) 
  11.         return
  12.  
  13.     dispatch({type: IS_LOADING}); 
  14.     // Yay, that could be sync or async dispatching 
  15.     remote.addFave(tweetId).then( 
  16.       (res) => { dispatch({type: ADD_FAVE_SUCCEED}) }, 
  17.       (err) => { dispatch({type: ADD_FAVE_FAILED, err: err}) }, 
  18.   }; 
  19.  
  20. store.dispatch(addFave()); 

這個例子演示了 “收藏” 一條微博的相關(guān)的 action 對象的產(chǎn)生過程。addFave 作為 Action Creator,返回的不是 Javascript Plain Object,而是一個接收 dispatch 和 getState作為參數(shù)的 Thunk 函數(shù)。

當(dāng) thunk Middleware 發(fā)現(xiàn)傳入的 action 是這樣的 Thunk 函數(shù)時(shí),就會為該函數(shù)配齊 dispatch 和 getState 參數(shù),讓 Thunk 函數(shù)得以執(zhí)行,否則,就調(diào)用 next(action)讓后續(xù) Middleware 獲得 dispatch 的機(jī)會。

在 Thunk 函數(shù)中,首先會判斷當(dāng)前應(yīng)用的 state 中的微博是否已經(jīng)被 fave 過了,如果沒有,才會調(diào)用遠(yuǎn)程方法。

如果調(diào)用遠(yuǎn)程方法的話,那么首先發(fā)出 IS_LOADING action,告訴 reducer 一個遠(yuǎn)程調(diào)用啟動了。從而讓 reducer 可以更新對應(yīng)的 state 屬性。這樣如果有關(guān)心此狀態(tài)的 UI Component 則可以據(jù)此更新界面。

遠(yuǎn)程方法如果調(diào)用成功,就會 dispatch 代表成功的 action 對象({type: ADD_FAVE_SUCCEED}),否則,產(chǎn)生的就是代表失敗的 action 對象({type: ADD_FAVE_FAILED, err: err})。無論如何,reducer 最后收到的 action 對象一定是這種 Javascript Plain Object。

當(dāng)Thunk Middleware 處理了 Thunk 函數(shù)類型的 action 之后,如果有配置了其他 Middlewares, 則將被跳過去而沒有機(jī)會執(zhí)行。

例如:我們的 Middlewares 配置為 applyMiddleware(logger, thunk, timeoutScheduler),當(dāng) action 是 Thunk 函數(shù)時(shí),這個 action 將沒有機(jī)會被 timeoutSchedulerMiddleware 執(zhí)行,而 logger Middleware 則有機(jī)會在 thunk Middleware 之前執(zhí)行。

applyMiddleware

拼裝 Middlewares 的工具函數(shù)是 applyMiddleware,該函數(shù)的模擬實(shí)現(xiàn)如下:

  1. function applyMiddleware(store, middlewares) { 
  2.   middlewares = middlewares.slice(); 
  3.   middlewares.reverse(); 
  4.  
  5.   let next = store.dispatch; 
  6.   middlewares.forEach(middleware => 
  7.     next = middleware(store)(next) 
  8.   ); 
  9.  
  10.   return Object.assign({}, store, { dispatch: next }); 

結(jié)合 Middleware 的寫法:

  1. const logger = store => next => action => { 
  2.   console.log('dispatching', action); 
  3.   let result = next(action); 
  4.   console.log('next state', store.getState()); 
  5.   return result; 
  6. }; 

我們可以看到,給 Middleware 傳入 store 和 next 之后,返回的是一個新的 dispatch 方法。而傳入的 next 參數(shù)則是之前 Middleware 返回的 dispatch 函數(shù)。這樣,在真正傳入 action 之前,我們得到了一個串聯(lián)在一起的 dispatch 函數(shù),該函數(shù)用來替代原本的store.dispatch 方法(通過 Object.assign(...))。Redux Middleware 機(jī)制的目的,就是以插件形式改變 store.dispatch 的行為方式,從而能夠處理不同類型的 action 輸入,得到最終的 Javascript Plain Object 形式的 action 對象。

每一個 Middleware 可以得到:

  1. 最初的 store 對象 (dispatch 屬性還是原來的),因此,可以通過 store.getState 獲得最近的狀態(tài),以及通過原本的 dispatch 對象直接發(fā)布 action 對象,跳過其他 Middleware dispatch 方法(next)。上面 vanillaPromise 演示了這樣的用法。
  2. next 方法: 前一個Middleware 返回的 dispatch 方法。當(dāng)前 Middleware 可以根據(jù)自己對 action 的判斷和處理結(jié)果,決定是否調(diào)用 next 方法,以及傳入什么樣的參數(shù)。

以 newStore = applyMiddleware(logger,thunk,timeoutScheduler)(store)) 這樣的聲明為例,timeoutScheduler 得到的next 參數(shù)就是原始的 store.dispatch 方法;thunk 擁有 timeoutScheduler 返回的 dispatch 方法,而 logger 又擁有 thunk 返回的 dispatch 方法。最后新生成的 newStore 的 dispatch 方法則是 logger 返回的。因此實(shí)際的 action流動的順序先到 logger 返回的 dispatch 方法,再到 thunk 返回的 dispatch 方法,最后到 timeoutScheduler 返回的 dispatch 方法。

需要注意一點(diǎn), logger 因?yàn)榕旁?nbsp;dispatch 鏈條的第一個,因此可以獲得進(jìn)入的每一個action 對象。但是由于其他 Middleware 有可能異步調(diào)用 dispatch (異步調(diào)用前一個 Middleware 返回的 dispatch 方法或者原始的 store.dispatch ),因此,logger 并一定有機(jī)會知道 action 最終是怎么傳遞的。

Middleware 可以有很多玩法的,下面文檔列出了 Middleware 的原理和七種Middlewares:http://rackt.github.io/redux/docs/advanced/Middleware.html

store/reducer 是 Redux 的最核心邏輯,而 Middleware 是其外圍的一種擴(kuò)展方式,僅負(fù)責(zé) action 對象的產(chǎn)生。但是由于 Redux 對于核心部分的限定非常嚴(yán)格(保持核心概念的簡單):例如,reducer 必須是同步的,實(shí)際工程需求所帶來的需求都被推到了 Dispatch/Middleware 這部分,官方文檔提到的使用方式則起到了”最佳實(shí)踐”的指導(dǎo)作用。

Higher-Order Store

Middleware 是對 store.dispatch 方法的擴(kuò)展機(jī)制。但有些時(shí)候則需要對整個 store 對象都進(jìn)行擴(kuò)充,這就引入了 Higher-Order Store 的概念。

這個概念和 React 的 Higher-Order Component 概念是類似的。https://github.com/gaearon/redux/blob/cdaa3e81ffdf49e25ce39eeed37affc8f0c590f7/docs/higher-order-stores.md ,既提供一個函數(shù),接受 store 對象作為輸入?yún)?shù),產(chǎn)生一個新的 store 對象作為返回值。

  1. createStore => createStore' 

Redux 建議大家在 Middleware 不能滿足擴(kuò)展要求的前提下再使用 Higher-Order Store,與 Redux 配套的 redux-devtools 就是一個例子。

Binding To React (React-Native)

上面的章節(jié)介紹了 Redux 的核心組組件和數(shù)據(jù)流程,可以通過下圖回味一下:

                                                                                      ┌──────────────┐
                        ┌─────────────┐                                           ┌──▶│ subReducer 1 │
                   ┌───▶│Middleware 1 │                                           │   └──────────────┘
                   │    └─────────────┘                                           │           │       
                   │           │                                                  │           ▼       
┌─────────────┐    │           │              ┌───────────────┐    ┌──────────┐   │   ┌──────────────┐
│   action'   │────┘           ▼          ┌──▶│store.dispatch │───▶│ reducer  │───┘   │ subReducer m │
└─────────────┘         ┌─────────────┐   │   └───────────────┘    └──────────┘       └──────────────┘
                        │Middleware n │   │                                                   │       
                        └─────────────┘   │                                                   │       
                               │          │                                                   ▼       
                               │          │                                           ┌──────────────┐
                               └──────────┘                                           │    state     │
                               plain action                                           └──────────────┘

Redux 解決的是應(yīng)用程序狀態(tài)存儲以及如何變更的問題,至于怎么用,則依賴于其他模塊。關(guān)于如何在 React 或者 React-Native 中使用 Redux ,則需要參考 react-redux

react-redux 是 React Components 如何使用 Redux 的 Binding。下面我們來分析一個具體的例子。

  1. import { Component } from 'react'
  2.  
  3. export default class Counter extends Component { 
  4.   render() { 
  5.     return ( 
  6.       <button onClick={this.props.onIncrement}> 
  7.         {this.props.value} 
  8.       </button> 
  9.     ); 
  10.   } 

這是一個 React Component,顯示了一個按鈕。按下這個按鈕,就會調(diào)用 this.props.onIncrement。onIncrement的具體內(nèi)容在下面的例子中, 起作用為每次調(diào)用 onIncrement就會 dispatch {type: INCREMENT} Action 對象來更新 Store/State。

在 react-redux 中,這樣的 Component 被稱為 “Dumb” Component,既其本身對 Redux 完全無知,它只知道從 this.props 獲取需要的 Action Creator 并且了解其語義,適當(dāng)?shù)臅r(shí)候調(diào)用該方法。而 “Dumb” Component 需要展現(xiàn)的外部數(shù)據(jù)也來自于 this.props。

如何為 “Dumb” Component 準(zhǔn)備 this.props 呢?react-redux 提供的 connect 函數(shù)幫助你完成這個功能:

  1. import { Component } from 'react'
  2. import { connect } from 'react-redux'
  3.  
  4. import Counter from '../components/Counter'
  5. import { increment } from '../actionsCreators'
  6.  
  7. // Which part of the Redux global state does our component want to receive as props? 
  8. function mapStateToProps(state) { 
  9.   return { 
  10.     value: state.counter 
  11.   }; 
  12.  
  13. // Which action creators does it want to receive by props? 
  14. function mapDispatchToProps(dispatch) { 
  15.   return { 
  16.     onIncrement: () => dispatch(increment()) 
  17.   }; 
  18.  
  19. export default connect(   // Line 20 
  20.   mapStateToProps, 
  21.   mapDispatchToProps 
  22. )(Counter); 

第 20 行的 connect將 state 的某個(些)屬性映射到了 Counter Component 的 this.props 屬性中,同時(shí)也把針對特定的Action Creator 的 dispatch 方法傳遞給了this.props。這樣在 Counter Component 中僅僅通過 this.props 就可以完成 action dispatching 和 應(yīng)用程序狀態(tài)獲取的動作。

如果 connect 函數(shù)省掉第二個參數(shù),connect(mapStateToProps)(Counter),那么 dispatch方法會被直接傳遞給 this.props。這不是推薦的方式,因?yàn)檫@意味著 Counter 需要了解dispatch 的功能和語義了。

Components 的嵌套

你可以在你的組件樹的任何一個層次調(diào)用 connect 來為下層組件綁定狀態(tài)和 dispatch方法。但是僅在你的頂層組件調(diào)用 connect 進(jìn)行綁定是首選的方法。

Provider Component

上面的例子實(shí)際上是不可執(zhí)行的,因?yàn)?nbsp;connect 函數(shù)其實(shí)并沒有 Redux store 對象在哪里。所以我們需要有一個機(jī)制讓 connect 知道從你那里獲得 store 對象,這是通過 Provider Component 來設(shè)定的,Provider Component 也是 react-redux 提供的工具組件。

  1. React.render( 
  2.   <Provider store={store}> 
  3.     {() => <MyRootComponent />} 
  4.   </Provider>, 
  5.   rootEl 
  6. ); 

Provider Component 應(yīng)該是你的 React Components 樹的根組件。由于 React 0.13 版本的問題,Provider Component 的子組件必須是一個函數(shù),這個問題將在 React 0.14 中修復(fù)。

Provider Component 和 connect 函數(shù)的配合,使得 React Component 在對 Redux 完全無感的情況下,僅通過 React 自身的機(jī)制來獲取和維護(hù)應(yīng)用程序的狀態(tài)。

selector

在上面的例子中,connect(mapStateToProps,mapDispatchToProps)(Counter) 中的 mapStateToProps 函數(shù)通過返回一個映射對象,指定了哪些 Store/State 屬性被映射到 React Component 的 this.props,這個方法被稱為 selector。selector 的作用就是為 React Components 構(gòu)造適合自己需要的狀態(tài)視圖。selector 的引入,降低了 React Component 對 Store/State 數(shù)據(jù)結(jié)構(gòu)的依賴,利于代碼解耦;同時(shí)由于 selector 的實(shí)現(xiàn)完全是自定義函數(shù),因此也有足夠的靈活性(例如對原始狀態(tài)數(shù)據(jù)進(jìn)行過濾、匯總等)。

reselect 這個項(xiàng)目提供了帶 cache 功能的 selector。如果 Store/State 和構(gòu)造 view 的參數(shù)沒有變化,那么每次 Component 獲取的數(shù)據(jù)都將來自于上次調(diào)用/計(jì)算的結(jié)果。得益于 Store/State Immutable 的本質(zhì),狀態(tài)變化的檢測是非常高效的。

總結(jié)

  1. Redux 和 React 沒有直接關(guān)系,它瞄準(zhǔn)的目標(biāo)是應(yīng)用狀態(tài)管理。
  2. 核心概念是 Map/Reduce 中的 Reduce。且 Reducer 的執(zhí)行是同步,產(chǎn)生的 State是 Immutable 的。
  3. 改變 State 只能通過向 Reducer dispatch actions 來完成。
  4. State 的不同字段,可以通過不同的 Reducers 來分別維護(hù)。combineReducers 負(fù)責(zé)組合這些 Reducers,前提是每個 Reducer 只能維護(hù)自己關(guān)心的字段。
  5. Action 對象只能是 Javascript Plain Object,但是通過在 store 上裝載middleware,可以非常靈活地產(chǎn)生 action 對象,并且以此為集中點(diǎn)實(shí)現(xiàn)很多控制邏輯,例如 Log,Undo, ErrorHandler 等。
  6. Redux 僅僅專注于應(yīng)用狀態(tài)的維護(hù),reducer、dispatch/middleware 是兩個常用擴(kuò)展點(diǎn)、Higher-order Store 則僅針對需要擴(kuò)展全部 Store 功能時(shí)使用。
  7. react-redux 是 Redux 針對 React/React-Native 的 Binding,connect/selector是擴(kuò)展點(diǎn)。
  8. Redux 是典型的函數(shù)式編程的產(chǎn)物,了解函數(shù)式編程會利于理解其實(shí)現(xiàn)原理,雖然使用它不需要了解很多函數(shù)式編程的概念。和 Flux 相比,Redux 的概念更精簡、約定更嚴(yán)格、狀態(tài)更確定、而是擴(kuò)展卻更靈活。
  9. 通過 https://github.com/xgrommx/awesome-redux 可以獲得大量參考。

其他參考

大而全的所有 Redux 參考資料。

https://github.com/xgrommx/awesome-redux

責(zé)任編輯:倪明 來源: 簡書
相關(guān)推薦

2021-02-19 08:38:36

Kubernetes容器化分布式

2020-10-23 09:26:57

React-Redux

2023-08-24 10:33:19

serviceexportsinfo類

2009-12-18 17:20:00

Ruby核心類

2022-03-10 13:11:11

DDD領(lǐng)域驅(qū)動設(shè)計(jì)

2025-05-15 08:05:00

2025-05-23 10:38:43

2023-01-03 08:31:54

Spring讀取器配置

2022-02-09 09:53:43

Spring框架開源

2022-01-27 13:47:10

Kubernete命令Linux

2019-05-28 12:03:59

vuejavascript前端

2021-11-14 23:06:49

Python代碼開發(fā)

2020-05-08 13:44:26

Spark架構(gòu)RDD

2023-10-22 23:28:34

2020-04-29 21:54:46

操作系統(tǒng)核心概念

2017-07-14 15:40:28

2017-04-25 09:50:16

SparkRDD核心

2018-11-08 15:12:16

數(shù)據(jù)分析算法決策樹

2024-11-11 08:00:00

PyTorch深度學(xué)習(xí)

2011-07-14 15:23:34

java
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

中文字幕av不卡| 日韩avvvv在线播放| 精品日韩av一区二区| 99在线观看视频免费| 嫩草研究院在线观看| 麻豆国产欧美日韩综合精品二区| 欧美成人精品一区二区| 欧美性xxxx图片| 色婷婷成人网| 图片区小说区区亚洲影院| 日韩av一区二区三区美女毛片| 国产精品自拍电影| 宅男噜噜噜66一区二区| 日韩三级影视基地| 在线观看国产网站| 精品女同一区二区三区在线观看| 亚洲国产美女搞黄色| 新呦u视频一区二区| 蜜桃视频久久一区免费观看入口| 日韩激情av在线| 欧美激情视频播放| 久久精品国产亚洲AV成人婷婷| 亚洲国产高清在线观看| 欧美性一级生活| av在线播放亚洲| 日本在线观看视频| 国产亚洲制服色| 成人动漫视频在线观看完整版| 波多野结衣一二区| 亚洲精品一二| 欧美精品免费在线| 波兰性xxxxx极品hd| 亚洲精品aaaaa| 亚洲精品一区二区在线观看| 成人不卡免费视频| 无人区在线高清完整免费版 一区二| 亚洲综合清纯丝袜自拍| 自拍偷拍99| 番号在线播放| 久久久精品日韩欧美| 国产私拍一区| xxxx国产精品| 国产一区二区0| 91精品国产综合久久久久久久久| 国产女主播喷水视频在线观看| 1024日韩| 97av在线影院| 日韩女同强女同hd| 91久久视频| 欧美黑人性猛交| 日韩在线观看视频一区二区| 五月激情综合| 久久夜色精品国产| 午夜爽爽爽男女免费观看| 大色综合视频网站在线播放| 夜夜躁日日躁狠狠久久88av| 性少妇bbw张开| 亚洲桃色综合影院| 亚洲片av在线| 影音先锋男人在线| 日韩一区二区在线免费| 日韩网站免费观看高清| caoporn91| 欧美日韩岛国| 国产综合在线视频| 天堂网一区二区三区| 国产视频欧美| 国产高清在线不卡| 中文字幕一区二区久久人妻| 美女视频一区二区| 91天堂在线观看| www.黄色一片| heyzo一本久久综合| 久久精品人成| 丁香在线视频| 亚洲欧美日韩成人高清在线一区| 国产日韩欧美大片| 不卡的av影片| 日本高清不卡视频| 嫩草视频免费在线观看| 奇米一区二区| 国产视频丨精品|在线观看| 国产交换配乱淫视频免费| 日韩成人激情| 久久99国产精品久久久久久久久| 国产在线拍揄自揄拍| 一本不卡影院| 国产精品免费久久久久久| 国产特级黄色片| 波波电影院一区二区三区| 欧美日韩高清免费| 毛片在线播放a| 偷拍亚洲欧洲综合| 中文字幕第88页| jizz性欧美23| 正在播放国产一区| 国产亚洲欧美精品久久久久久| 亚洲在线网站| 亚洲va久久久噜噜噜| 天天综合网在线观看| 国产精品成人免费在线| 男女私大尺度视频| 国产在视频一区二区三区吞精| 日韩欧美成人激情| 无码少妇精品一区二区免费动态| 一个色综合网| 国产成人在线精品| 亚洲女同志亚洲女同女播放| 亚洲国产精品成人久久综合一区| 精品视频在线观看一区二区| 日韩精品一区二区三区av| 日韩欧美激情在线| 国产精品久久久久久成人| 国一区二区在线观看| 国产在线999| 黄色小视频在线免费观看| 一区二区三区四区五区视频在线观看| 日日摸天天爽天天爽视频| 日韩精品一区二区三区免费视频| 亚洲小视频在线观看| 国产亚洲第一页| 久久成人免费网站| 日本精品免费| 国产高清自产拍av在线| 欧美一区二区网站| gv天堂gv无码男同在线观看| 国产精品永久| 国产精品中出一区二区三区| 二区三区四区高清视频在线观看| 色欧美片视频在线观看在线视频| 9191在线视频| 亚洲激情五月| 国产精品无av码在线观看| 四虎在线免费看| 亚洲二区在线视频| 熟妇女人妻丰满少妇中文字幕| 欧美色图国产精品| 日本一区二区不卡| 天堂av在线7| 天天综合天天综合色| 日本wwww色| 欧美激情1区| 成人国产在线激情| 麻豆传媒在线观看| 精品视频一区二区三区免费| 中文字幕免费高清| 爽好久久久欧美精品| 免费精品视频一区二区三区| 性欧美18~19sex高清播放| 亚洲精品91美女久久久久久久| 久久久久久免费观看| 国产成人久久精品77777最新版本 国产成人鲁色资源国产91色综 | 日韩限制级电影在线观看| 日本黄色录像视频| 国产做a爰片久久毛片| 宅男噜噜99国产精品观看免费| 国产成人免费精品| 日韩中文字幕在线看| 国产日韩免费视频| 一区二区三区免费网站| 亚洲女则毛耸耸bbw| 一区免费在线| 久久99热只有频精品91密拍| 性欧美又大又长又硬| 亚洲人成伊人成综合网久久久| 神马久久久久久久| 欧美激情一区二区三区四区| jizz18女人| 牛夜精品久久久久久久99黑人| 97超级在线观看免费高清完整版电视剧| 亚洲婷婷噜噜| 精品视频偷偷看在线观看| 久久精品偷拍视频| 亚洲欧美综合另类在线卡通| 善良的小姨在线| 一区二区三区四区五区在线 | 日本网站在线看| 欧美色123| 免费日韩av电影| 激情中国色综合| 久久久欧美精品| 国产午夜视频在线观看| 在线播放欧美女士性生活| 久久免费精彩视频| 久久九九久精品国产免费直播| 91女神在线观看| 国产精品黄色| 欧美三级网色| 日韩在线视频一区二区三区| 国产69精品久久久久99| seseavlu视频在线| 欧美成人aa大片| 国产精华7777777| 亚洲一区二区在线视频| 亚洲v国产v欧美v久久久久久| 韩国理伦片一区二区三区在线播放| 国产va亚洲va在线va| 精品一区二区三| 懂色中文一区二区三区在线视频| 成人免费看视频网站| 九九久久精品一区| а天堂8中文最新版在线官网| 欧美成人精品1314www| 中文字幕第三页| 婷婷亚洲久悠悠色悠在线播放| 国产又黄又粗又猛又爽的| 99在线精品一区二区三区| 97超碰人人爽| 久久精品九九| 国产主播自拍av| 我不卡影院28| 亚洲国产精品123| 丝袜美腿综合| 97se国产在线视频| 欧美jizz18| 国产成人拍精品视频午夜网站| 黄色成人在线网| 久久天天躁狠狠躁夜夜躁2014| 黄色在线播放| 亚洲欧美精品一区| 婷婷五月综合久久中文字幕| 日韩三级精品电影久久久| 一本一道精品欧美中文字幕| 色悠悠久久综合| 亚洲精品国产精品乱码| 亚洲一区二区欧美| 很污很黄的网站| 中文字幕第一区二区| caopeng视频| 久久网站热最新地址| 特大黑人巨人吊xxxx| 成人免费观看av| 亚洲免费观看在线| 国产a区久久久| 国产chinesehd精品露脸| 国产在线国偷精品免费看| 亚洲美女性囗交| 久久国产精品区| 日韩av一卡二卡三卡| 美腿丝袜亚洲一区| 日日干夜夜操s8| 久久激情五月婷婷| 免费av不卡在线| 狠狠狠色丁香婷婷综合激情 | 国产美女永久无遮挡| 欧美精品97| 精品无码一区二区三区爱欲| 欧美日韩蜜桃| 国产精品久久久久久久久电影网| 欧美xxx在线观看| 亚洲乱码日产精品bd在线观看| 欧美福利网址| 成人一级生活片| 亚洲激情视频| 无码人妻丰满熟妇区96| 免费欧美在线| 一级在线免费视频| 精品在线免费视频| 亚洲区 欧美区| av中文一区二区三区| 激情综合丁香五月| 中文字幕成人av| 色哟哟一一国产精品| 亚洲精品视频观看| 日韩少妇裸体做爰视频| 欧美性xxxxx极品娇小| 国产精品高清无码| 91精品国产综合久久久久久漫画| 国产成人免费看一级大黄| 欧美精品一区二区三区蜜桃| 人人九九精品| 中文字幕亚洲欧美| 天堂亚洲精品| 欧美一级黄色网| 欧美亚洲黄色| 国产乱码精品一区二区三区不卡| 亚洲成人一品| 亚洲一区二区在| 亚洲午夜激情在线| 免费大片在线观看| 国产一区二区在线免费观看| 男人网站在线观看| 中文字幕av一区二区三区高 | 欧美成人hd| 久久久久久尹人网香蕉| 日韩美女在线看免费观看| 91在线免费网站| 亚洲激情77| 艳母动漫在线观看| 中文国产一区| 日本中文字幕影院| 99久久国产综合精品色伊| 久久精品三级视频| 亚洲午夜久久久久久久久电影网| 精品成人无码久久久久久| 欧美一级国产精品| 狠狠v欧美ⅴ日韩v亚洲v大胸| 欧美老女人xx| 国产成人午夜性a一级毛片| 国产伦精品一区二区三| 青草国产精品| 国产原创中文在线观看| 精品制服美女久久| 丰满少妇一区二区| 亚洲一区二区视频| 在线观看日批视频| 亚洲精品国产欧美| 色婷婷在线播放| 国产精品视频自拍| 丝袜美腿综合| 人体内射精一区二区三区| 狠狠色丁香婷婷综合久久片| 性欧美13一14内谢| 午夜不卡在线视频| www.亚洲天堂.com| 久久人人爽亚洲精品天堂| 成人欧美一区二区三区的电影| 岛国视频一区| 欧美成人日韩| 国内自拍第二页| 中文字幕av资源一区| 夜夜爽妓女8888视频免费观看| 精品国产乱码久久久久久免费| 国产一二三区在线观看| 国产精品小说在线| 欧美精品乱码| www.日本xxxx| 国产午夜精品理论片a级大结局| 日本污视频在线观看| 欧美成人video| 欧美aaa免费| 97久久人人超碰caoprom欧美| 99久久婷婷| 久久婷五月综合| 欧美激情一区二区三区不卡| 无码日韩精品一区二区| 亚洲欧美激情视频| 韩漫成人漫画| 日韩欧美在线观看强乱免费| 久久午夜精品一区二区| www在线观看免费视频| 色综合天天综合网天天狠天天| 天堂资源中文在线| 欧亚精品在线观看| 欧美男同视频网| 久草在在线视频| 国产日韩欧美综合在线| 精品国产青草久久久久96| 最近2019免费中文字幕视频三| 成人高清一区| 中文一区一区三区免费| 国产九色精品成人porny| 久久久久久福利| 亚洲成人精品久久久| www.九色在线| 欧美日韩精品免费观看视一区二区| 久久精品免费| 丰满的亚洲女人毛茸茸| 欧美精品九九99久久| 国产视频在线播放| 国产精品久久一区二区三区| 亚洲精品偷拍| 真实乱视频国产免费观看 | 成人在线免费播放视频| 国产日韩欧美麻豆| 国产精品嫩草影院精东| 欧美精品一区三区| 久久综合另类图片小说| 久久美女福利视频| 国产精品美女久久福利网站| 国产露脸91国语对白| 久久久久久久久久久人体| 欧美成人基地| 中国黄色片免费看| 亚洲色大成网站www久久九九| 亚洲国产精品18久久久久久| 2023亚洲男人天堂| 日韩在线观看| 国产在线不卡av| 欧洲av一区二区嗯嗯嗯啊| 国产区在线观看| 精品日产一区2区三区黄免费 | 成人综合网网址| 狠狠爱成人网| 欧美做受xxxxxⅹ性视频| 在线不卡a资源高清| 91色在线看| 亚洲图片在线观看| 成人激情动漫在线观看| 亚洲天堂手机在线| 久久久久久久久久久av| 欧美一区三区| 日韩aaaaa| 在线播放91灌醉迷j高跟美女 | 国产免费嫩草影院| 亚洲第一区第一页| 日韩黄色三级在线观看| 成人毛片视频网站| 亚洲免费观看高清完整版在线观看熊| 天堂在线资源网|