成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

手寫一Redux,深入理解其原理

開發(fā) 前端
Redux可是一個大名鼎鼎的庫,很多地方都在用,我也用了幾年了,今天這篇文章就是自己來實現(xiàn)一個Redux,以便于深入理解他的原理。

手寫一Redux,深入理解其原理

Redux可是一個大名鼎鼎的庫,很多地方都在用,我也用了幾年了,今天這篇文章就是自己來實現(xiàn)一個Redux,以便于深入理解他的原理。我們還是老套路,從基本的用法入手,然后自己實現(xiàn)一個Redux來替代源碼的NPM包,但是功能保持不變。本文只會實現(xiàn)Redux的核心庫,跟其他庫的配合使用,比如React-Redux準備后面單獨寫一篇文章來講。有時候我們過于關注使用,只記住了各種使用方式,反而忽略了他們的核心原理,但是如果我們想真正的提高技術,最好還是一個一個搞清楚,比如Redux和React-Redux看起來很像,但是他們的核心理念和關注點是不同的,Redux其實只是一個單純狀態(tài)管理庫,沒有任何界面相關的東西,React-Redux關注的是怎么將Redux跟React結合起來,用到了一些React的API。

本文全部代碼已經上傳到GitHub,大家可以拿下來玩下:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/redux

基本概念

Redux的概念有很多文章都講過,想必大家都看過很多了,我這里不再展開,只是簡單提一下。Redux基本概念主要有以下幾個:

Store

人如其名,Store就是一個倉庫,它存儲了所有的狀態(tài)(State),還提供了一些操作他的API,我們后續(xù)的操作其實都是在操作這個倉庫。假如我們的倉庫是用來放牛奶的,初始情況下,我們的倉庫里面一箱牛奶都沒有,那Store的狀態(tài)(State)就是: 

  1.  
  2.     milk: 0  

Actions

一個Action就是一個動作,這個動作的目的是更改Store中的某個狀態(tài),Store還是上面的那個倉庫,現(xiàn)在我想往倉庫放一箱牛奶,那"我想往倉庫放一箱牛奶"就是一個Action,代碼就是這樣: 

  1.  
  2.   type: "PUT_MILK",  
  3.   count: 1  

Reducers

前面"我想往倉庫放一箱牛奶"只是想了,還沒操作,具體操作要靠Reducer,Reducer就是根據接收的Action來改變Store中的狀態(tài),比如我接收了一個PUT_MILK,同時數量count是1,那放進去的結果就是milk增加了1,從0變成了1,代碼就是這樣: 

  1. const initState = {  
  2.   milk: 0  
  3.  
  4. function reducer(state = initState, action) {  
  5.   switch (action.type) {  
  6.     case 'PUT_MILK':  
  7.       return {...state, milk: state.milk + action.count}  
  8.     default:  
  9.       return state  
  10.   }  

可以看到Redux本身就是一個單純的狀態(tài)機,Store存放了所有的狀態(tài),Action是一個改變狀態(tài)的通知,Reducer接收到通知就更改Store中對應的狀態(tài)。

簡單例子

下面我們來看一個簡單的例子,包含了前面提到的Store,Action和Reducer這幾個概念: 

  1. import { createStore } from 'redux';  
  2. const initState = {  
  3.   milk: 0  
  4. };  
  5. function reducer(state = initState, action) {  
  6.   switch (action.type) {  
  7.     case 'PUT_MILK': 
  8.        return {...state, milk: state.milk + action.count};  
  9.     case 'TAKE_MILK':  
  10.       return {...state, milk: state.milk - action.count};  
  11.     default:  
  12.       return state;  
  13.   }  
  14.  
  15. let store = createStore(reducer);  
  16. // subscribe其實就是訂閱store的變化,一旦store發(fā)生了變化,傳入的回調函數就會被調用  
  17. // 如果是結合頁面更新,更新的操作就是在這里執(zhí)行  
  18. store.subscribe(() => console.log(store.getState()));  
  19. // 將action發(fā)出去要用dispatch  
  20. store.dispatch({ type: 'PUT_MILK' });    // milk: 1  
  21. store.dispatch({ type: 'PUT_MILK' });    // milk: 2  
  22. store.dispatch({ type: 'TAKE_MILK' });   // milk: 1 

自己實現(xiàn)

前面我們那個例子雖然短小,但是已經包含了Redux的核心功能了,所以我們手寫的第一個目標就是替換這個例子中的Redux。要替換這個Redux,我們得先知道他里面都有什么東西,仔細一看,我們好像只用到了他的一個API:

createStore:這個API接受reducer方法作為參數,返回一個store,主要功能都在這個store上。

看看store上我們都用到了啥:

store.subscribe: 訂閱state的變化,當state變化的時候執(zhí)行回調,可以有多個subscribe,里面的回調會依次執(zhí)行。

store.dispatch: 發(fā)出action的方法,每次dispatch action都會執(zhí)行reducer生成新的state,然后執(zhí)行subscribe注冊的回調。

store.getState:一個簡單的方法,返回當前的state。

看到subscribe注冊回調,dispatch觸發(fā)回調,想到了什么,這不就是發(fā)布訂閱模式嗎?我之前有一篇文章詳細講過發(fā)布訂閱模式了,這里直接仿寫一個。 

  1. function createStore() {  
  2.   let state;              // state記錄所有狀態(tài)  
  3.   let listeners = [];     // 保存所有注冊的回調  
  4.   function subscribe(callback) {  
  5.     listeners.push(callback);       // subscribe就是將回調保存下來  
  6.   }  
  7.   // dispatch就是將所有的回調拿出來依次執(zhí)行就行  
  8.   function dispatch() {  
  9.     for (let i = 0; i < listeners.length; i++) {  
  10.       const listener = listeners[i];  
  11.       listener();  
  12.     }  
  13.   }  
  14.   // getState直接返回state  
  15.   function getState() {  
  16.     return state;  
  17.   }  
  18.   // store包裝一下前面的方法直接返回  
  19.   const store = {  
  20.     subscribe,  
  21.     dispatch,  
  22.     getState  
  23.   }  
  24.   return store;  

上述代碼是不是很簡單嘛,Redux核心也是一個發(fā)布訂閱模式,就是這么簡單!等等,好像漏了啥,reducer呢?reducer的作用是在發(fā)布事件的時候改變state,所以我們的dispatch在執(zhí)行回調前應該先執(zhí)行reducer,用reducer的返回值重新給state賦值,dispatch改寫如下: 

  1. function dispatch(action) {  
  2.   state = reducer(state, action);  
  3.   for (let i = 0; i < listeners.length; i++) {  
  4.     const listener = listeners[i];  
  5.     listener();  
  6.   }  

到這里,前面例子用到的所有API我們都自己實現(xiàn)了,我們用自己的Redux來替換下官方的Redux試試: 

  1. // import { createStore } from 'redux';  
  2. import { createStore } from './myRedux'; 

可以看到輸出結果是一樣的,說明我們自己寫的Redux沒有問題:

了解了Redux的核心原理,我們再去看他的源碼應該就沒有問題了,createStore的源碼傳送門

最后我們再來梳理下Redux的核心流程,注意單純的Redux只是個狀態(tài)機,是沒有View層的哦。

除了這個核心邏輯外,Redux里面還有些API也很有意思,我們也來手寫下。

手寫combineReducers

combineReducers也是使用非常廣泛的API,當我們應用越來越復雜,如果將所有邏輯都寫在一個reducer里面,最終這個文件可能會有成千上萬行,所以Redux提供了combineReducers,可以讓我們?yōu)椴煌哪K寫自己的reducer,最終將他們組合起來。比如我們最開始那個牛奶倉庫,由于我們的業(yè)務發(fā)展很好,我們又增加了一個放大米的倉庫,我們可以為這兩個倉庫創(chuàng)建自己的reducer,然后將他們組合起來,使用方法如下: 

  1. import { createStore, combineReducers } from 'redux';  
  2. const initMilkState = {  
  3.   milk: 0  
  4. };  
  5. function milkReducer(state = initMilkState, action) {  
  6.   switch (action.type) {  
  7.     case 'PUT_MILK':  
  8.       return {...state, milk: state.milk + action.count};  
  9.     case 'TAKE_MILK':  
  10.       return {...state, milk: state.milk - action.count};  
  11.     default:  
  12.       return state;  
  13.   }  
  14.  
  15. const initRiceState = {  
  16.   rice: 0  
  17. };  
  18. function riceReducer(state = initRiceState, action) {  
  19.   switch (action.type) {  
  20.     case 'PUT_RICE':  
  21.       return {...state, rice: state.rice + action.count};  
  22.     case 'TAKE_RICE':  
  23.       return {...state, rice: state.rice - action.count};  
  24.     default:  
  25.       return state;  
  26.   }  
  27.  
  28. // 使用combineReducers組合兩個reducer  
  29. const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});  
  30. let store = createStore(reducer);  
  31. store.subscribe(() => console.log(store.getState()));  
  32. // 操作🥛的action  
  33. store.dispatch({ type: 'PUT_MILK', count: 1 });    // milk: 1  
  34. store.dispatch({ type: 'PUT_MILK', count: 1 });    // milk: 2  
  35. store.dispatch({ type: 'TAKE_MILK', count: 1 });   // milk: 1  
  36. // 操作大米的action  
  37. store.dispatch({ type: 'PUT_RICE', count: 1 });    // rice: 1  
  38. store.dispatch({ type: 'PUT_RICE', count: 1 });    // rice: 2  
  39. store.dispatch({ type: 'TAKE_RICE', count: 1 });   // rice: 1 

上面代碼我們將大的state分成了兩個小的milkState和riceState,最終運行結果如下:

知道了用法,我們嘗試自己來寫下呢!要手寫combineReducers,我們先來分析下他干了啥,首先它的返回值是一個reducer,這個reducer同樣會作為createStore的參數傳進去,說明這個返回值是一個跟我們之前普通reducer結構一樣的函數。這個函數同樣接收state和action然后返回新的state,只是這個新的state要符合combineReducers參數的數據結構。我們嘗試來寫下: 

  1. function combineReducers(reducerMap) {  
  2.   const reducerKeys = Object.keys(reducerMap);    // 先把參數里面所有的鍵值拿出來  
  3.   // 返回值是一個普通結構的reducer函數  
  4.   const reducer = (state = {}, action) => {  
  5.     const newState = {};    
  6.     for(let i = 0; i < reducerKeys.length; i++) {  
  7.       // reducerMap里面每個鍵的值都是一個reducer,我們把它拿出來運行下就可以得到對應鍵新的state值  
  8.       // 然后將所有reducer返回的state按照參數里面的key組裝好  
  9.       // 最后再返回組裝好的newState就行  
  10.       const key = reducerKeys[i];  
  11.       const currentReducer = reducerMap[key];  
  12.       const prevState = state[key]; 
  13.        newState[key] = currentReducer(prevState, action);  
  14.     }  
  15.      return newState;  
  16.   };   
  17.   return reducer;  

官方源碼的實現(xiàn)原理跟我們的一樣,只是他有更多的錯誤處理,大家可以對照著看下。

手寫applyMiddleware

middleware是Redux里面很重要的一個概念,Redux的生態(tài)主要靠這個API接入,比如我們想寫一個logger的中間件可以這樣寫(這個中間件來自于官方文檔): 

  1. // logger是一個中間件,注意返回值嵌了好幾層函數  
  2. // 我們后面來看看為什么這么設計  
  3. function logger(store) {  
  4.   return function(next) {  
  5.     return function(action) {  
  6.       console.group(action.type);  
  7.       console.info('dispatching', action);  
  8.       let result = next(action);  
  9.       console.log('next state', store.getState());  
  10.       console.groupEnd();  
  11.       return result  
  12.     }  
  13.   }  
  14. // 在createStore的時候將applyMiddleware作為第二個參數傳進去  
  15. const store = createStore 
  16.   reducer,  
  17.   applyMiddleware(logger)  

可以看到上述代碼為了支持中間件,createStore支持了第二個參數,這個參數官方稱為enhancer,顧名思義他是一個增強器,用來增強store的能力的。官方對于enhancer的定義如下: 

  1. type StoreEnhancer = (next: StoreCreator) => StoreCreator 

上面的結構的意思是說enhancer作為一個函數,他接收StoreCreator函數作為參數,同時返回的也必須是一個StoreCreator函數。注意他的返回值也是一個StoreCreator函數,也就是我們把他的返回值拿出來繼續(xù)執(zhí)行應該得到跟之前的createStore一樣的返回結構,也就是說我們之前的createStore返回啥結構,他也必須返回結構,也就是這個store: 

  1.  
  2.   subscribe,  
  3.   dispatch,  
  4.   getState  

createStore支持enhancer

根據他關于enhancer的定義,我們來改寫下自己的createStore,讓他支持enhancer: 

  1. function createStore(reducer, enhancer) {   // 接收第二個參數enhancer  
  2.   // 先處理enhancer  
  3.   // 如果enhancer存在并且是函數  
  4.   // 我們將createStore作為參數傳給他  
  5.   // 他應該返回一個新的createStore給我  
  6.   // 我再拿這個新的createStore執(zhí)行,應該得到一個store  
  7.   // 直接返回這個store就行  
  8.   if(enhancer && typeof enhancer === 'function'){  
  9.     const newCreateStore = enhancer(createStore);  
  10.     const newStore = newCreateStore(reducer);  
  11.     return newStore;  
  12.   }  
  13.   // 如果沒有enhancer或者enhancer不是函數,直接執(zhí)行之前的邏輯  
  14.   // 下面這些代碼都是之前那版  
  15.   // 省略n行代碼  
  16.     // .......  
  17.   const store = {  
  18.     subscribe,  
  19.     dispatch,  
  20.     getState  
  21.   }  
  22.   return store;  

這部分對應的源碼看這里。

applyMiddleware返回值是一個enhancer

前面我們已經有了enhancer的基本結構,applyMiddleware是作為第二個參數傳給createStore的,也就是說他是一個enhancer,準確的說是applyMiddleware的返回值是一個enhancer,因為我們傳給createStore的是他的執(zhí)行結果applyMiddleware(): 

  1. function applyMiddleware(middleware) {  
  2.   // applyMiddleware的返回值應該是一個enhancer  
  3.   // 按照我們前面說的enhancer的參數是createStore  
  4.   function enhancer(createStore) { 
  5.      // enhancer應該返回一個新的createStore  
  6.     function newCreateStore(reducer) {  
  7.       // 我們先寫個空的newCreateStore,直接返回createStore的結果  
  8.       const store = createStore(reducer);  
  9.       return store  
  10.     }    
  11.     return newCreateStore;  
  12.   } 
  13.    return enhancer;  

實現(xiàn)applyMiddleware

上面我們已經有了applyMiddleware的基本結構了,但是功能還沒實現(xiàn),要實現(xiàn)他的功能,我們必須先搞清楚一個中間件到底有什么功能,還是以前面的logger中間件為例: 

  1. function logger(store) {  
  2.   return function(next) {  
  3.     return function(action) {  
  4.       console.group(action.type);  
  5.       console.info('dispatching', action);  
  6.       let result = next(action);  
  7.       console.log('next state', store.getState());  
  8.       console.groupEnd();  
  9.       return result  
  10.     }  
  11.   }  

這個中間件運行效果如下:

可以看到我們let result = next(action);這行執(zhí)行之后state改變了,前面我們說了要改變state只能dispatch(action),所以這里的next(action)就是dispatch(action),只是換了一個名字而已。而且注意最后一層返回值return function(action)的結構,他的參數是action,是不是很像dispatch(action),其實他就是一個新的dispatch(action),這個新的dispatch(action)會調用原始的dispatch,并且在調用的前后加上自己的邏輯。所以到這里一個中間件的結構也清楚了:

  1.   一個中間件接收store作為參數,會返回一個函數
  2.   返回的這個函數接收老的dispatch函數作為參數,會返回一個新的函數
  3.   返回的新函數就是新的dispatch函數,這個函數里面可以拿到外面兩層傳進來的store和老dispatch函數

所以說白了,中間件就是加強dispatch的功能,用新的dispatch替換老的dispatch,這不就是個裝飾者模式嗎?其實前面enhancer也是一個裝飾者模式,傳入一個createStore,在createStore執(zhí)行前后加上些代碼,最后又返回一個增強版的createStore。可見設計模式在這些優(yōu)秀的框架中還真是廣泛存在,如果你對裝飾者模式還不太熟悉,可以看我之前這篇文章。

遵循這個思路,我們的applyMiddleware就可以寫出來了: 

  1. // 直接把前面的結構拿過來  
  2. function applyMiddleware(middleware) {  
  3.   function enhancer(createStore) {  
  4.     function newCreateStore(reducer) {  
  5.       const store = createStore(reducer);   
  6.       // 將middleware拿過來執(zhí)行下,傳入store  
  7.       // 得到第一層函數 
  8.        const func = middleware(store);     
  9.        // 解構出原始的dispatch  
  10.       const { dispatch } = store;      
  11.        // 將原始的dispatch函數傳給func執(zhí)行  
  12.       // 得到增強版的dispatch  
  13.       const newDispatch = func(dispatch);      
  14.        // 返回的時候用增強版的newDispatch替換原始的dispatch  
  15.       return {...store, dispatch: newDispatch}  
  16.     }    
  17.      return newCreateStore;  
  18.   } 
  19.    return enhancer;  

照例用我們自己的applyMiddleware替換老的,跑起來是一樣的效果,說明我們寫的沒問題,哈哈~

支持多個middleware

我們的applyMiddleware還差一個功能,就是支持多個middleware,比如像這樣: 

  1. applyMiddleware(  
  2.   rafScheduler,  
  3.   timeoutScheduler,  
  4.   thunk,  
  5.   vanillaPromise,  
  6.   readyStatePromise,  
  7.   logger,  
  8.   crashReporter  

其實要支持這個也簡單,我們返回的newDispatch里面依次的將傳入的middleware拿出來執(zhí)行就行,多個函數的串行執(zhí)行可以使用輔助函數compose,這個函數定義如下。只是需要注意的是我們這里的compose不能把方法拿來執(zhí)行就完了,應該返回一個包裹了所有方法的方法。 

  1. function compose(...func){  
  2.   return funcs.reduce((a, b) => (...args) => a(b(...args)));  

這個compose可能比較讓人困惑,我這里還是講解下,比如我們有三個函數,這三個函數都是我們前面接收dispatch返回新dispatch的方法: 

  1. const fun1 = dispatch => newDispatch1;  
  2. const fun2 = dispatch => newDispatch2;  
  3. const fun3 = dispatch => newDispatch3; 

當我們使用了compose(fun1, fun2, fun3)后執(zhí)行順序是什么樣的呢? 

  1. // 第一次其實執(zhí)行的是  
  2. (func1, func2) => (...args) => func1(fun2(...args))  
  3. // 這次執(zhí)行完的返回值是下面這個,用個變量存起來吧  
  4. const temp = (...args) => func1(fun2(...args))  
  5. // 我們下次再循環(huán)的時候其實執(zhí)行的是  
  6. (temp, func3) => (...args) => temp(func3(...args));  
  7. // 這個返回值是下面這個,也就是最終的返回值,其實就是從func3開始從右往左執(zhí)行完了所有函數  
  8. // 前面的返回值會作為后面參數 
  9.  (...args) => temp(func3(...args));  
  10. // 再看看上面這個方法,如果把dispatch作為參數傳進去會是什么效果  
  11. (dispatch) => temp(func3(dispatch)); 
  12. // 然后func3(dispatch)返回的是newDispatch3,這個又傳給了temp(newDispatch3),也就是下面這個會執(zhí)行  
  13. (newDispatch3) => func1(fun2(newDispatch3))  
  14. // 上面這個里面用newDispatch3執(zhí)行fun2(newDispatch3)會得到newDispatch2  
  15. // 然后func1(newDispatch2)會得到newDispatch1  
  16. // 注意這時候的newDispatch1其實已經包含了newDispatch3和newDispatch2的邏輯了,將它拿出來執(zhí)行這三個方法就都執(zhí)行了 

更多關于compose原理的細節(jié)可以看我之前這篇文章。

所以我們支持多個middleware的代碼就是這樣: 

  1. // 參數支持多個中間件  
  2. function applyMiddleware(...middlewares) {  
  3.   function enhancer(createStore) {  
  4.     function newCreateStore(reducer) {  
  5.       const store = createStore(reducer);      
  6.       // 多個middleware,先解構出dispatch => newDispatch的結構  
  7.       const chain = middlewares.map(middleware => middleware(store));
  8.        const { dispatch } = store;       
  9.        // 用compose得到一個組合了所有newDispatch的函數  
  10.       const newDispatchGen = compose(...chain);  
  11.       // 執(zhí)行這個函數得到newDispatch  
  12.       const newDispatch = newDispatchGen(dispatch);  
  13.       return {...store, dispatch: newDispatch}  
  14.     }    
  15.      return newCreateStore;  
  16.   } 
  17.    return enhancer; 
  18.  

最后我們再加一個logger2中間件實現(xiàn)效果: 

  1. function logger2(store) {  
  2.   return function(next) {  
  3.     return function(action) {  
  4.       let result = next(action);  
  5.       console.log('logger2');  
  6.       return result  
  7.     }  
  8.   }  
  9. let store = createStore(reducer, applyMiddleware(logger, logger2)); 

可以看到logger2也已經打印出來了,大功告成。

現(xiàn)在我們也可以知道他的中間件為什么要包裹幾層函數了:

第一層:目的是傳入store參數

第二層:第二層的結構是dispatch => newDispatch,多個中間件的這層函數可以compose起來,形成一個大的dispatch => newDispatch

第三層:這層就是最終的返回值了,其實就是newDispatch,是增強過的dispatch,是中間件的真正邏輯所在。

到這里我們的applyMiddleware就寫完了,對應的源碼可以看這里,相信看了本文再去看源碼就沒啥問題了!

本文所有代碼已經傳到GitHub,大家可以去拿下來玩一下:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/React/redux

總結

  1.  單純的Redux只是一個狀態(tài)機,store里面存了所有的狀態(tài)state,要改變里面的狀態(tài)state,只能dispatch action。
  2.  對于發(fā)出來的action需要用reducer來處理,reducer會計算新的state來替代老的state。
  3.  subscribe方法可以注冊回調方法,當dispatch action的時候會執(zhí)行里面的回調。
  4.  Redux其實就是一個發(fā)布訂閱模式!
  5.  Redux還支持enhancer,enhancer其實就是一個裝飾者模式,傳入當前的createStore,返回一個增強的createStore。
  6.  Redux使用applyMiddleware支持中間件,applyMiddleware的返回值其實就是一個enhancer。
  7.  Redux的中間件也是一個裝飾者模式,傳入當前的dispatch,返回一個增強了的dispatch。
  8.  單純的Redux是沒有View層的,所以他可以跟各種UI庫結合使用,比如react-redux,計劃下一篇文章就是手寫react-redux。 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2022-04-26 08:32:36

CSS前端

2021-12-01 19:32:14

原理Node代碼

2024-03-12 00:00:00

Sora技術數據

2022-11-04 09:43:05

Java線程

2021-03-10 10:55:51

SpringJava代碼

2024-11-01 08:57:07

2022-09-05 08:39:04

kubernetesk8s

2024-04-15 00:00:00

技術Attention架構

2020-08-10 18:03:54

Cache存儲器CPU

2021-07-16 07:57:34

ReduxDOM組件

2020-03-26 16:40:07

MySQL索引數據庫

2023-09-19 22:47:39

Java內存

2022-01-14 12:28:18

架構OpenFeign遠程

2022-09-26 08:01:31

線程LIFO操作方式

2020-11-04 15:35:13

Golang內存程序員

2020-03-17 08:36:22

數據庫存儲Mysql

2019-07-01 13:34:22

vue系統(tǒng)數據

2023-10-13 13:30:00

MySQL鎖機制

2022-09-05 22:22:00

Stream操作對象

2021-09-26 05:03:31

數據流Redux
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中国一级大黄大片 | 小川阿佐美pgd-606在线 | 亚洲精品在线观看视频 | 午夜一级做a爰片久久毛片 精品综合 | 国际精品鲁一鲁一区二区小说 | 欧美操操操 | 久久久久成人精品 | 亚洲精品福利在线 | 人人澡人人射 | 国产精品永久 | 羞羞的视频在线看 | 蜜桃免费一区二区三区 | 亚洲 欧美 日韩 在线 | 国产欧美一区二区三区久久 | aaaa日韩| 欧美精品91 | 亚洲综合三区 | 亚洲一区二区三区欧美 | av片在线观看网站 | 女女百合av大片一区二区三区九县 | 精品国产欧美 | 久久久精品在线 | 国产精品久久久久久久久免费高清 | 在线观看国产视频 | 亚洲欧美激情精品一区二区 | 日韩精品 | 国产精品一区二区视频 | 日本一区二区三区四区 | 精品视频一区二区三区在线观看 | 亚洲第1页| 日韩精品一区二区三区四区 | 操操日| 久久伊人免费视频 | 国产美女黄色片 | 日韩久久久久久 | 成人在线视频观看 | 久久丝袜| 国产高清视频一区 | 欧美成人综合 | 国产日韩欧美一区二区在线播放 | 一区二区在线视频 |