日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Redux 进阶 - react 全家桶学习笔记(二)

發布時間:2025/3/17 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redux 进阶 - react 全家桶学习笔记(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

注:這篇是17年1月的文章,搬運自本人 blog...

https://github.com/BuptStEve/...

零、前言

在上一篇中介紹了 Redux 的各項基礎 api。接著一步一步地介紹如何與 React 進行結合,并從引入過程中遇到的各個痛點引出 react-redux 的作用和原理。

不過目前為止還都是紙上談兵,在日常的開發中最常見異步操作(如通過 ajax、jsonp 等方法 獲取數據),在學習完上一篇后你可能依然沒有頭緒。因此本文將深入淺出地對于 redux 的進階用法進行介紹。

一、中間件(MiddleWare)

It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. ———— by Dan Abramov

這是 redux 作者對 middleware 的描述,middleware 提供了一個分類處理 action 的機會,在 middleware 中你可以檢閱每一個流過的 action,挑選出特定類型的 action 進行相應操作,給你一次改變 action 的機會。

說得好像很吊...不過有啥用咧...?

1. 日志應用場景[[2]]

因為改變 store 的唯一方法就是 dispatch 一個 action,所以有時需要將每次 dispatch 操作都打印出來作為操作日志,這樣一來就可以很容易地看出是哪一次 dispatch 導致了異常。

1.1. 第一次嘗試:強行懟...

const action = addTodo('Use Redux');console.log('dispatching', action); store.dispatch(action); console.log('next state', store.getState());

顯然這種在每一個 dispatch 操作的前后都手動加代碼的方法,簡直讓人不忍直視...

1.2. 第二次嘗試:封裝 dispatch

聰明的你一定馬上想到了,不如將上述代碼封裝成一個函數,然后直接調用該方法。

function dispatchAndLog(store, action) {console.log('dispatching', action);store.dispatch(action);console.log('next state', store.getState()); }dispatchAndLog(store, addTodo('Use Redux'));

矮油,看起來不錯喲。

不過每次使用都需要導入這個額外的方法,一旦不想使用又要全部替換回去,好麻煩啊...

1.3. 第三次嘗試:猴子補丁(Monkey Patch)

在此暫不探究為啥叫猴子補丁而不是什么其他補丁。

簡單來說猴子補丁指的就是:以替換原函數的方式為其添加新特性或修復 bug。

let next = store.dispatch; // 暫存原方法store.dispatch = function dispatchAndLog(action) {console.log('dispatching', action);let result = next(action); // 應用原方法console.log('next state', store.getState());return result; };

這樣一來我們就“偷梁換柱”般的為原 dispatch 添加了輸出日志的功能。

1.4. 第四次嘗試:隱藏猴子補丁

目前看起來很不錯,然鵝假設我們又要添加別的一個中間件,那么代碼中將會有重復的 let next = store.dispatch; 代碼。

對于這個問題我們可以通過參數傳遞,返回新的 dispatch 來解決。

function logger(store) {const next = store.dispatch;return function dispatchAndLog(action) {console.log('dispatching', action);const result = next(action); // 應用原方法console.log('next state', store.getState());return result;} }store.dispatch = logger(store); store.dispatch = anotherMiddleWare(store);

注意到最后應用中間件的代碼其實就是一個鏈式的過程,所以還可以更進一步優化綁定中間件的過程。

function applyMiddlewareByMonkeypatching(store, middlewares) {// 因為傳入的是原對象引用的值,slice 方法會生成一份拷貝,// 所以之后調用的 reverse 方法不會改變原數組middlewares = middlewares.slice();// 我們希望按照數組原本的先后順序觸發各個中間件,// 所以最后的中間件應當最接近原本的 dispatch,// 就像洋蔥一樣一層一層地包裹原 dispatchmiddlewares.reverse();// 在每一個 middleware 中變換 store.dispatch 方法。middlewares.forEach((middleware) =>store.dispatch = middleware(store);); }// 先觸發 logger,再觸發 anotherMiddleWare 中間件(類似于 koa 的中間件機制) applyMiddlewareByMonkeypatching(store, [ logger, anotherMiddleWare ]);

so far so good~! 現在不僅隱藏了顯式地緩存原 dispatch 的代碼,而且調用起來也很優雅~,然鵝這樣就夠了么?

1.5. 第五次嘗試:移除猴子補丁

注意到,以上寫法仍然是通過 store.dispatch = middleware(store); 改寫原方法,并在中間件內部通過 const next = store.dispatch; 讀取當前最新的方法。

本質上其實還是 monkey patch,只不過將其封裝在了內部,不過若是將 dispatch 方法通過參數傳遞進來,這樣在 applyMiddleware 函數中就可以暫存 store.dispatch(而不是一次又一次的改寫),豈不美哉?

// 通過參數傳遞 function logger(store, next) {return function dispatchAndLog(action) {// ...} }function applyMiddleware(store, middlewares) {// ...// 暫存原方法let dispatch = store.dispatch;// middleware 中通過閉包獲取 dispatch,并且更新 dispatchmiddlewares.forEach((middleware) =>dispatch = middleware(store, dispatch);); }

接著應用函數式編程的 curry 化(一種使用匿名單參數函數來實現多參數函數的方法。),還可以再進一步優化。(其實是為了使用 compose 將中間件函數先組合再綁定)

function logger(store) {return function(next) {return function(action) {console.log('dispatching', action);const result = next(action); // 應用原方法console.log('next state', store.getState());return result;}} }// -- 使用 es6 的箭頭函數可以讓代碼更加優雅更函數式... -- const logger = (store) => (next) => (action) => {console.log('dispatching', action);const result = next(action); // 應用原方法console.log('next state', store.getState());return result; };function applyMiddleware(store, middlewares) {// ...let dispatch = store.dispatch;middlewares.forEach((middleware) =>dispatch = middleware(store)(dispatch); // 注意調用了兩次);// ... }

以上方法離 Redux 中最終的 applyMiddleware 實現已經很接近了,

1.6. 第六次嘗試:組合(compose,函數式方法)

在 Redux 的最終實現中,并沒有采用我們之前的 slice + reverse 的方法來倒著綁定中間件。而是采用了 map + compose + reduce 的方法。

先來說這個 compose 函數,在數學中以下等式十分的自然。

f(g(x)) = (f o g)(x)
f(g(h(x))) = (f o g o h)(x)

用代碼來表示這一過程就是這樣。

// 傳入參數為函數數組 function compose(...funcs) {// 返回一個閉包,// 將右邊的函數作為內層函數執行,并將執行結果作為外層函數再次執行return funcs.reduce((a, b) => (...args) => a(b(...args))); }

不了解 reduce 函數的人可能對于以上代碼會感到有些費解,舉個栗子來說,有函數數組 [f, g, h]傳入 compose 函數執行。

  • 首次 reduce 執行的結果是返回一個函數 (...args) => f(g(...args))
  • 接著該函數作為下一次 reduce 函數執行時的參數 a,而參數 b 是 h
  • 再次執行時 h(...args) 作為參數傳入 a,即最后返回的還是一個函數 (...args) => f(g(h(...args)))

因此最終版 applyMiddleware 實現中并非依次執行綁定,而是采用函數式的思維,將作用于 dispatch 的函數首先進行組合,再進行綁定。(所以要中間件要 curry 化)

// 傳入中間件函數的數組 function applyMiddleware(...middlewares) {// 返回一個函數的原因在 createStore 部分再進行介紹return (createStore) => (reducer, preloadedState, enhancer) => {const store = createStore(reducer, preloadedState, enhancer)let dispatch = store.dispatchlet chain = [] // 保存綁定了 middlewareAPI 后的函數數組const middlewareAPI = {getState: store.getState,dispatch: (action) => dispatch(action)}chain = middlewares.map(middleware => middleware(middlewareAPI))// 使用 compose 函數按照從右向左的順序綁定(執行順序是從左往右)dispatch = compose(...chain)(store.dispatch)return {...store,dispatch}} }// store -> { getState } 從傳遞整個 store 改為傳遞部分 api const logger = ({ getState }) => (next) => (action) => {console.log('dispatching', action);const result = next(action); // 應用原方法console.log('next state', getState());return result; };

綜上如下圖所示整個中間件的執行順序是類似于洋蔥一樣首先按照從外到內的順序執行 dispatch 之前的中間件代碼,在 dispatch(洋蔥的心)執行后又反過來,按照從內到左外的順序執行 dispatch 之后的中間件代碼。

橋都麻袋!

你真的都理解了么?

  • 在之前的實現中直接傳遞 store,為啥在最終實現中傳遞的是 middlewareAPI?
  • middlewareAPI 里的 dispatch 是為啥一個匿名函數而不直接傳遞 dispatch?
  • 如下列代碼所示,如果在中間件里不用 next 而是調用 store.dispatch 會怎樣呢?
const logger = (store) => (next) => (action) => {console.log('dispatching', action);// 調用原始 dispatch,而不是上一個中間件傳進來的const result = store.dispatch(action); // <- 這里console.log('next state', store.getState());return result; };

1.7. middleware 中調用 store.dispatch[[6]]

正常情況下,如圖左,當我們 dispatch 一個 action 時,middleware 通過 next(action) 一層一層處理和傳遞 action 直到 redux 原生的 dispatch。如果某個 middleware 使用 store.dispatch(action) 來分發 action,就發生了右圖的情況,相當于從外層重新來一遍,假如這個 middleware 一直簡單粗暴地調用 store.dispatch(action),就會形成無限循環了。(其實就相當于猴子補丁沒補上,不停地調用原來的函數)

因此最終版里不是直接傳遞 store,而是傳遞 getState 和 dispatch,傳遞 getState 的原因是可以通過 getState 獲取當前狀態。并且還將 dispatch 用一個匿名函數包裹 dispatch: (action) => dispatch(action),這樣不但可以防止 dispatch 被中間件修改,而且只要 dispatch 更新了,middlewareAPI 中的 dispatch 也會隨之發生變化。

1.8. createStore 進階

在上一篇中我們使用 createStore 方法只用到了它前兩個參數,即 reducer 和 preloadedState,然鵝其實它還擁有第三個參數 enhancer。

enhancer 參數可以實現中間件、時間旅行、持久化等功能,Redux 僅提供了 applyMiddleware 用于應用中間件(就是 1.6. 中的那個)。

在日常使用中,要應用中間件可以這么寫。

import {createStore,combineReducers,applyMiddleware, } from 'redux';// 組合 reducer const rootReducer = combineReducers({todos: todosReducer,filter: filterReducer, });// 中間件數組 const middlewares = [logger, anotherMiddleWare];const store = createStore(rootReducer,initialState,applyMiddleware(...middlewares), );// 如果不需要 initialState 的話也可以忽略 const store = createStore(rootReducer,applyMiddleware(...middlewares), );

在上文 applyMiddleware 的實現中留了個懸念,就是為什么返回的是一個函數,因為 enhancer 被定義為一個高階函數,接收 createStore 函數作為參數。

/*** 創建一個 redux store 用于保存狀態樹,* 唯一改變 store 中數據的方法就是對其調用 dispatch** 在你的應用中應該只有一個 store,想要針對不同的部分狀態響應 action,* 你應該使用 combineReducers 將多個 reducer 合并。** @param {函數} reducer 不多解釋了* @param {對象} preloadedState 主要用于前后端同構時的數據同步* @param {函數} enhancer 很牛逼,可以實現中間件、時間旅行,持久化等* ※ Redux 僅提供 applyMiddleware 這個 Store Enhancer ※* @return {Store}*/ export default function createStore(reducer, preloadedState, enhancer) {if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = preloadedStatepreloadedState = undefined}if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}// enhancer 是一個高階函數,接收 createStore 函數作為參數return enhancer(createStore)(reducer, preloadedState)}// ...// 后續內容推薦看看參考資料部分的【Redux 莞式教程】 }

總的來說 Redux 有五個 API,分別是:

  • createStore(reducer, [initialState], enhancer)
  • combineReducers(reducers)
  • applyMiddleware(...middlewares)
  • bindActionCreators(actionCreators, dispatch)
  • compose(...functions)

createStore 生成的 store 有四個 API,分別是:

  • getState()
  • dispatch(action)
  • subscribe(listener)
  • replaceReducer(nextReducer)

以上 API 我們還沒介紹的應該就剩 bindActionCreators 了。這個 API 其實就是個語法糖起了方便地給 action creator 綁定 dispatch 的作用。

// 一般寫法 function mapDispatchToProps(dispatch) {return {onPlusClick: () => dispatch(increment()),onMinusClick: () => dispatch(decrement()),}; }// 使用 bindActionCreators import { bindActionCreators } from 'redux';function mapDispatchToProps(dispatch) {return bindActionCreators({onPlusClick: increment,onMinusClick: decrement,// 還可以綁定更多函數...}, dispatch); }// 甚至如果定義的函數輸入都相同的話還能更加簡潔 export default connect(mapStateToProps,// 直接傳一個對象,connect 自動幫你綁定 dispatch{ onPlusClick: increment, onMinusClick: decrement }, )(App);

二、異步操作

下面讓我們告別干凈的同步世界,進入“骯臟”的異步世界~。

在函數式編程中,異步操作、修改全局變量等與函數外部環境發生的交互叫做副作用(Side Effect)
通常認為這些操作是邪惡(evil)骯臟(dirty)的,并且也是導致 bug 的源頭。
因為與之相對的是純函數(pure function),即對于同樣的輸入總是返回同樣的輸出的函數,使用這樣的函數很容易做組合、測試等操作,很容易驗證和保證其正確性。(它們就像數學公式一般準確)

2.1. 通知應用場景[[3]]

現在有這么一個顯示通知的應用場景,在通知顯示后5秒鐘隱藏該通知。

首先當然是編寫 action

  • 顯示:SHOW_NOTIFICATION
  • 隱藏:HIDE_NOTIFICATION

2.1.1. 最直觀的寫法

最直觀的寫法就是首先顯示通知,然后使用 setTimeout 在5秒后隱藏通知。

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' }); setTimeout(() => {store.dispatch({ type: 'HIDE_NOTIFICATION' }); }, 5000);

然鵝,一般在組件中尤其是展示組件中沒法也沒必要獲取 store,因此一般將其包裝成 action creator。

// actions.js export function showNotification(text) {return { type: 'SHOW_NOTIFICATION', text }; } export function hideNotification() {return { type: 'HIDE_NOTIFICATION' }; }// component.js import { showNotification, hideNotification } from '../actions';this.props.dispatch(showNotification('You just logged in.')); setTimeout(() => {this.props.dispatch(hideNotification()); }, 5000);

或者更進一步地先使用 connect 方法包裝。

this.props.showNotification('You just logged in.'); setTimeout(() => {this.props.hideNotification(); }, 5000);

到目前為止,我們沒有用任何 middleware 或者別的概念。

2.1.2. 異步 action creator

上一種直觀寫法有一些問題

  • 每當我們需要顯示一個通知就需要手動先顯示,然后再手動地讓其消失。其實我們更希望通知到時間后自動地消失。
  • 通知目前沒有自己的 id,所以有些場景下存在競爭條件(race condition),即假如在第一個通知結束前觸發第二個通知,當第一個通知結束時,第二個通知也會被提前關閉。

所以為了解決以上問題,我們可以為通知加上 id,并將顯示和消失的代碼包起來。

// actions.js const showNotification = (text, id) => ({type: 'SHOW_NOTIFICATION',id,text, }); const hideNotification = (id) => ({type: 'HIDE_NOTIFICATION',id, });let nextNotificationId = 0; export function showNotificationWithTimeout(dispatch, text) {const id = nextNotificationId++;dispatch(showNotification(id, text));setTimeout(() => {dispatch(hideNotification(id));}, 5000); }// component.js showNotificationWithTimeout(this.props.dispatch, 'You just logged in.');// otherComponent.js showNotificationWithTimeout(this.props.dispatch, 'You just logged out.');

為啥 showNotificationWithTimeout 函數要接收 dispatch 作為第一個參數呢?
雖然通常一個組件都擁有觸發 dispatch 的權限,但是現在我們想讓一個外部函數(showNotificationWithTimeout)來觸發 dispatch,所以需要將 dispatch 作為參數傳入。

2.1.3. 單例 store

可能你會說如果有一個從其他模塊中導出的單例 store,那么是不是同樣也可以不傳遞 dispatch 以上代碼也可以這樣寫。

// store.js export default createStore(reducer);// actions.js import store from './store';// ...let nextNotificationId = 0; export function showNotificationWithTimeout(text) {const id = nextNotificationId++;store.dispatch(showNotification(id, text));setTimeout(() => {store.dispatch(hideNotification(id));}, 5000); }// component.js showNotificationWithTimeout('You just logged in.');// otherComponent.js showNotificationWithTimeout('You just logged out.');

這樣看起來似乎更簡單一些,不過墻裂不推薦這樣的寫法。主要的原因是這樣的寫法強制讓 store 成為一個單例。這樣一來要實現服務器端渲染(Server Rendering)將十分困難。因為在服務端,為了讓不同的用戶得到不同的預先獲取的數據,你需要讓每一個請求都有自己的 store。

并且單例 store 也將讓測試變得困難。當測試 action creator 時你將無法自己模擬一個 store,因為它們都引用了從外部導入的那個特定的 store,所以你甚至無法從外部重置狀態。

2.1.4. redux-thunk 中間件

首先聲明 redux-thunk 這種方案對于小型的應用來說足夠日常使用,然鵝對于大型應用來說,你可能會發現一些不方便的地方。(例如對于 action 需要組合、取消、競爭等復雜操作的場景)

首先來明確什么是 thunk...

A thunk is a function that wraps an expression to delay its evaluation.

簡單來說 thunk 就是封裝了表達式的函數,目的是延遲執行該表達式。不過有啥應用場景呢?

目前為止,在上文中的 2.1.2. 異步 action creator 部分,最后得出的方案有以下明顯的缺點

  • 我們必須將 dispatch 作為參數傳入。
  • 這樣一來任何使用了異步操作的組件都必須用 props 傳遞 dispatch(不管有多深...)。我們也沒法像之前各種同步操作一樣使用 connect 函數來綁定回調函數,因為 showNotificationWithTimeout 函數返回的不是一個 action。
  • 此外,在日常使用時,我們還需要區分哪些函數是同步的 action creator,那些是異步的 action creator。(異步的需要傳 dispatch...)

    • 同步的情況: store.dispatch(actionCreator(payload))
    • 異步的情況: asyncActionCreator(store.dispatch, payload)
計將安出?

其實問題的本質在于 Redux “有眼不識 function”,目前為止 dispatch 函數接收的參數只能是 action creator 返回的普通的 action。所以如果我們讓 dispatch 對于 function 網開一面,走走后門潛規則一下不就行啦~

實現方式很簡單,想想第一節介紹的為 dispatch 添加日志功能的過程。

// redux-thunk 源碼 function createThunkMiddleware(extraArgument) {return ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument);}return next(action);}; }const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware;export default thunk;

以上就是 redux-thunk 的源碼,就是這么簡單,判斷下如果傳入的 action 是函數的話,就執行這個函數...(withExtraArgument 是為了添加額外的參數,詳情見 redux-thunk 的 README.md)

  • 這樣一來如果我們 dispatch 了一個函數,redux-thunk 會傳給它一個 dispatch 參數,我們就利用 thunk 解決了組件中不方便獲取 dispatch 的問題。
  • 并且由于 redux-thunk 攔截了函數,也可以防止 reducer 接收到函數而出現異常。

添加了 redux-thunk 中間件后代碼可以這么寫。

// actions.js // ...let nextNotificationId = 0; export function showNotificationWithTimeout(text) {// 返回一個函數return function(dispatch) {const id = nextNotificationId++;dispatch(showNotification(id, text));setTimeout(() => {dispatch(hideNotification(id));}, 5000);}; }// component.js 像同步函數一樣的寫法 this.props.dispatch(showNotificationWithTimeout('You just logged in.'));// 或者 connect 后直接調用 this.props.showNotificationWithTimeout('You just logged in.');

2.2. 接口應用場景

目前我們對于簡單的延時異步操作的處理已經了然于胸了,現在讓我們來考慮一下通過 ajax 或 jsonp 等接口來獲取數據的異步場景。

很自然的,我們會發起一個請求,然后等待請求的響應(請求可能成功或是失敗)。

即有基本的三種狀態和與之對應的 action:

  • 請求開始的 action:isFetching 為真,UI 顯示加載界面

{ type: 'FETCH_POSTS_REQUEST' }

  • 請求成功的 action:isFetching 為假,隱藏加載界面并顯示接收到的數據

{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

  • 請求失敗的 action:isFetching 為假,隱藏加載界面,可能保存失敗信息并在 UI 中顯示出來

{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }

按照這個思路,舉一個簡單的栗子。

// Constants const FETCH_POSTS_REQUEST = 'FETCH_POSTS_REQUEST'; const FETCH_POSTS_SUCCESS = 'FETCH_POSTS_SUCCESS'; const FETCH_POSTS_FAILURE = 'FETCH_POSTS_FAILURE';// Actions const requestPosts = (id) => ({type: FETCH_POSTS_REQUEST,payload: id, });const receivePosts = (res) => ({type: FETCH_POSTS_SUCCESS,payload: res, });const catchPosts = (err) => ({type: FETCH_POSTS_FAILURE,payload: err, });const fetchPosts = (id) => (dispatch, getState) => {dispatch(requestPosts(id));return api.getData(id).then(res => dispatch(receivePosts(res))).catch(error => dispatch(catchPosts(error))); };// reducer const reducer = (oldState, action) => {switch (action.type) {case FETCH_POSTS_REQUEST:return requestState;case FETCH_POSTS_SUCCESS:return successState;case FETCH_POSTS_FAILURE:return errorState;default:return oldState;} };

盡管這已經是最簡單的調用接口場景,我們甚至還沒寫一行業務邏輯代碼,但講道理的話代碼還是比較繁瑣的。

而且其實代碼是有一定的“套路”的,比如其實整個代碼都是針對請求、成功、失敗三部分來處理的,這讓我們自然聯想到 Promise,同樣也是分為 pending、fulfilled、rejected 三種狀態。

那么這兩者可以結合起來讓模版代碼精簡一下么?

2.2.1. redux-promise 中間件[[8]]

首先開門見山地使用 redux-promise 中間件來改寫之前的代碼看看效果。

// Constants const FETCH_POSTS_REQUEST = 'FETCH_POSTS_REQUEST';// Actions const fetchPosts = (id) => ({type: FETCH_POSTS_REQUEST,payload: api.getData(id), // payload 為 Promise 對象 });// reducer const reducer = (oldState, action) => {switch (action.type) {case FETCH_POSTS_REQUEST:// requestState 被“吃掉”了// 而成功、失敗的狀態通過 status 來判斷if (action.status === 'success') {return successState;} else {return errorState;}default:return oldState;} };

可以看出 redux-promise 中間件比較激進、比較原教旨。

不但將發起請求的初始狀態被攔截了(原因見下文源碼),而且使用 action.status 而不是 action.type 來區分兩個 action 這一做法也值得商榷(個人傾向使用 action.type 來判斷)。

// redux-promise 源碼 import { isFSA } from 'flux-standard-action';function isPromise(val) {return val && typeof val.then === 'function'; }export default function promiseMiddleware({ dispatch }) {return next => action => {if (!isFSA(action)) {return isPromise(action)? action.then(dispatch): next(action);}return isPromise(action.payload)// 直接調用 Promise.then(所以發不出請求開始的 action)? action.payload.then(// 自動 dispatchresult => dispatch({ ...action, payload: result }),// 自動 dispatcherror => {dispatch({ ...action, payload: error, error: true });return Promise.reject(error);}): next(action);}; }

以上是 redux-promise 的源碼,十分簡單。主要邏輯是判斷如果是 Promise 就執行 then 方法。此外還根據是不是 FSA 決定調用的是 action 本身還是 action.payload 并且對于 FSA 會自動 dispatch 成功和失敗的 FSA。

2.2.2. redux-promise-middleware 中間件

盡管 redux-promise 中間件節省了大量代碼,然鵝它的缺點除了攔截請求開始的 action,以及使用 action.status 來判斷成功失敗狀態以外,還有就是由此引申出的一個無法實現的場景————樂觀更新(Optimistic Update)。

樂觀更新比較直觀的栗子就是在微信、QQ等通訊軟件中,發送的消息立即在對話窗口中展示,如果發送失敗了,在消息旁邊展示提示即可。由于在這種交互方式中“樂觀”地相信操作會成功,因此稱作樂觀更新。

因為樂觀更新發生在用戶發起操作時,所以要實現它,意味著必須有表示用戶初始動作的 action。

因此為了解決這些問題,相對于比較原教旨的 redux-promise 來說,更加溫和派一點的 redux-promise-middleware 中間件應運而生。先看看代碼怎么說。

// Constants const FETCH_POSTS = 'FETCH_POSTS'; // 前綴// Actions const fetchPosts = (id) => ({type: FETCH_POSTS, // 傳遞的是前綴,中間件會自動生成中間狀態payload: {promise: api.getData(id),data: id,}, });// reducer const reducer = (oldState, action) => {switch (action.type) {case `${FETCH_POSTS}_PENDING`:return requestState; // 可通過 action.payload.data 獲取 idcase `${FETCH_POSTS}_FULFILLED`:return successState;case `${FETCH_POSTS}_REJECTED`:return errorState;default:return oldState;} };

如果不需要樂觀更新,fetchPosts 函數可以更加簡潔。

// 此時初始 actionGET_DATA_PENDING 仍然會觸發,但是 payload 為空。 const fetchPosts = (id) => ({type: FETCH_POSTS, // 傳遞的是前綴payload: api.getData(id), // 等價于 payload: { promise: api.getData(id) }, });

相對于 redux-promise 簡單粗暴地直接過濾初始 action,從 reducer 可以看出,redux-promise-middleware 會首先自動觸發一個 FETCH_POSTS_PENDING 的 action,以此保留樂觀更新的能力。

并且,在狀態的區分上,回歸了通過 action.type 來判斷狀態的“正途”,其中 _PENDING、_FULFILLED、_REJECTED 后綴借用了 Promise 規范 (當然它們是可配置的) 。

后綴可以配置全局或局部生效,例如全局配置可以這么寫。 applyMiddleware(promiseMiddleware({promiseTypeSuffixes: ['LOADING', 'SUCCESS', 'ERROR']}) )

源碼地址點我,類似 redux-promise 也是在中間件中攔截了 payload 中有 Promise 的 action,并主動 dispatch 三種狀態的 action,注釋也很詳細在此就不贅述了。

注意:redux-promise、redux-promise-middleware 與 redux-thunk 之間并不是互相替代的關系,而更像一種補充優化。

2.3. redux-loop 中間件

簡單小結一下,Redux 的數據流如下所示:

UI => action => action creator => reducer => store => react => v-dom => UI

redux-thunk 的思路是保持 action 和 reducer 簡單純粹,然鵝副作用操作(在前端主要體現在異步操作上)的復雜度是不可避免的,因此它將其放在了 action creator 步驟,通過 thunk 函數手動控制每一次的 dispatch。

redux-promise 和 redux-promise-middleware 只是在其基礎上做一些輔助性的增強,處理異步的邏輯本質上是相同的,即將維護復雜異步操作的責任推到了用戶的身上。

這種實現方式固然很好理解,而且理論上可以應付所有異步場景,但是由此帶來的問題就是模版代碼太多,一旦流程復雜那么異步代碼就會到處都是,很容易導致出現 bug。

因此有一些其他的中間件,例如 redux-loop 就將異步處理邏輯放在 reducer 中。(Redux 的思想借鑒了 Elm,注意并不是“餓了么”,而 Elm 就是將異步處理放在 update(reducer) 層中)。

Synchronous state transitions caused by returning a new state from the reducer in response to an action are just one of all possible effects an action can have on application state.
這種通過響應一個 action,在 reducer 中返回一個新 state,從而引起同步狀態轉換的方式,只是在應用狀態中一個 action 能擁有的所有可能影響的一種。(可能沒翻好~歡迎勘誤~)

redux-loop 認為許多其他的處理異步的中間件,尤其是通過 action creator 方式實現的中間件,錯誤地讓用戶認為異步操作從根本上與同步操作并不相同。這樣一來無形中鼓勵了中間件以許多特殊的方式來處理異步狀態。

與之相反,redux-loop 專注于讓 reducer 變得足夠強大以便處理同步和異步操作。在具體實現上 reducer 不僅能夠根據特定的 action 決定當前的轉換狀態,而且還能決定接著發生的操作。

應用中所有行為都可以在一個地方(reducer)中被追蹤,并且這些行為可以輕易地分割和組合。(redux 作者 Dan 開了個至今依然 open 的 issue:Reducer Composition with Effects in JavaScript,討論關于對 reducer 進行分割組合的問題。)

redux-loop 模仿 Elm 的模式,引入了 Effect 的概念,在 reducer 中對于異步等操作使用 Effect 來處理。如下官方示例所示:

import { Effects, loop } from 'redux-loop';function fetchData(id) {return fetch(`endpoint/${id}`).then((r) => r.json()).then((data) => ({ type: 'FETCH_SUCCESS', payload: data })).catch((error) => ({ type: 'FETCH_FAILURE', payload: error.message })); }function reducer(state, action) {switch(action.type) {case 'FETCH_START':return loop( // <- 并沒有直接返回 state,實際上了返回數組 [state, effect]{ ...state, loading: true },Effects.promise(fetchData, action.payload.id));case 'FETCH_SUCCESS':return { ...state, loading: false, data: action.payload };case 'FETCH_FAILURE':return { ...state, loading: false, errorMessage: action.payload };} }

雖然這個想法很 Elm 很函數式,不過由于修改了 reducer 的返回類型,這樣一來會導致許多已有的 Api 和第三方庫無法使用,甚至連 redux 庫中的 combineReducers 方法都需要使用 redux-loop 提供的定制版本。因此這也是 redux-loop 最終無法轉正的原因:

"If a solution doesn’t work with vanilla combineReducers(), it won’t get into Redux core."

三、復雜異步操作

3.1. 更復雜的通知場景[[9]]

讓我們的思路重新回到通知的場景,之前的代碼實現了:

  • 展示一個通知并在數秒后消失
  • 可以同時展示多個通知。

現在假設可親可愛的產品又提出了新需求:

  • 同時不展示多于3個的通知
  • 如果已有3個通知正在展示,此時的新通知請求將排隊延遲展示。
“這個實現不了...”(全文完)

這個當然可以實現,只不過如果只用之前的 redux-thunk 實現起來會很麻煩。例如可以在 store 中增加兩個數組分別表示當前展示列表和等待隊列,然后在 reducer 中手動控制各個狀態時這倆數組的變化。

3.2. redux-saga 中間件

首先來看看使用了 redux-saga 后代碼會變成怎樣~(代碼來自生產環境的某 app)

function* toastSaga() {const MaxToasts = 3;const ToastDisplayTime = 4000;let pendingToasts = []; // 等待隊列let activeToasts = []; // 展示列表function* displayToast(toast) {if ( activeToasts >= MaxToasts ) {throw new Error("can't display more than " + MaxToasts + " at the same time");}activeToasts = [...activeToasts, toast]; // 新增通知到展示列表yield put(events.toastDisplayed(toast)); // 展示通知yield call(delay, ToastDisplayTime); // 通知的展示時間yield put(events.toastHidden(toast)); // 隱藏通知activeToasts = _.without(activeToasts,toast); // 從展示列表中刪除}function* toastRequestsWatcher() {while (true) {const event = yield take(Names.TOAST_DISPLAY_REQUESTED); // 監聽通知展示請求const newToast = event.data.toastData;pendingToasts = [...pendingToasts, newToast]; // 將新通知放入等待隊列}}function* toastScheduler() {while (true) {if (activeToasts.length < MaxToasts && pendingToasts.length > 0) {const [firstToast,...remainingToasts] = pendingToasts;pendingToasts = remainingToasts;yield fork(displayToast, firstToast); // 取出隊頭的通知進行展示// 增加一點延遲,這樣一來兩個并發的通知請求不會同時展示yield call(delay, 300);}else {yield call(delay, 50);}}}yield [call(toastRequestsWatcher),call(toastScheduler)] }// reducer const reducer = (state = {toasts: []}, event) => {switch (event.name) {case Names.TOAST_DISPLAYED:return {...state,toasts: [...state.toasts, event.data.toastData]};case Names.TOAST_HIDDEN:return {...state,toasts: _.without(state.toasts, event.data.toastData)};default:return state;} };

先不要在意代碼的細節,簡單分析一下上述代碼的邏輯:

  • store 上只有一個 toasts 節點,且 reducer 十分干凈
  • 排隊等具體的業務邏輯都放到了 toastSaga 函數中

    • displayToast 函數負責單個通知的展示和消失邏輯
    • toastRequestsWatcher 函數負責監聽請求,將其加入等待隊列
    • toastScheduler 函數負責將等待隊列中的元素加入展示列表

基于這樣邏輯分離的寫法,還可以繼續滿足更加復雜的需求:

  • 如果在等待隊列中有太多通知,動態減少通知的展示時間
  • 根據窗口大小的變化,改變最多展示的通知數量
  • ...

redux-saga V.S. redux-thunk[[11]]
redux-saga 的優點:

  • 易于測試,因為 redux-saga 中所有操作都 yield 簡單對象,所以測試只要判斷返回的對象是否正確即可,而測試 thunk 通常需要你在測試中引入一個 mockStore
  • redux-saga 提供了一些方便的輔助方法。(takeLatest、cancel、race 等)
  • 在 saga 函數中處理業務邏輯和異步操作,這樣一來通常代碼更加清晰,更容易增加和更改功能
  • 使用 ES6 的 generator,以同步的方式寫異步代碼

redux-saga 的缺點:

  • generator 的語法("又是 * 又是 yield 的,很難理解誒~")
  • 學習曲線陡峭,有許多概念需要學習("fork、join 這不是進程的概念么?這些 yield 是以什么順序執行的?")
  • API 的穩定性,例如新增了 channel 特性,并且社區也不是很大。
通知場景各種中間件寫法的完整代碼可以看這里

3.3. 理解 Saga Pattern[[14]]

3.3.1. Saga 是什么

Sagas 的概念來源于這篇論文,該論文從數據庫的角度談了 Saga Pattern。

Saga 就是能夠滿足特定條件的長事務(Long Lived Transaction)

暫且不提這個特定條件是什么,首先一般學過數據庫的都知道事務(Transaction)是啥~

如果不知道的話可以用轉賬來理解,A 轉給 B 100 塊錢的操作需要保證完成 A 先減 100 塊錢然后 B 加 100 塊錢這兩個操作,這樣才能保證轉賬前后 A 和 B 的存款總額不變。
如果在給 B 加 100 塊錢的過程中發生了異常,那么就要返回轉賬前的狀態,即給 A 再加上之前減的 100 塊錢(不然錢就不翼而飛了),這樣的一次轉賬(要么轉成功,要么失敗返回轉賬前的狀態)就是一個事務。

3.3.2. 長事務的問題

長事務顧名思義就是一個長時間的事務。

一般來說是通過給正在進行事務操作的對象加鎖,來保證事務并發時不會出錯。

例如 A 和 B 都給 C 轉 100 塊錢。

  • 如果不加鎖,極端情況下 A 先轉給 C 100 塊,而 B 讀取到了 C 轉賬前的數值,這時 B 的轉賬會覆蓋 A 的轉賬,C 只加了 100 塊錢,另 100 塊不翼而飛了。
  • 如果加了鎖,這時 B 的轉賬會等待 A 的轉賬完成后再進行。所以 C 能正確地收到 200 塊錢。
以押尾光太郎的指彈演奏會售票舉例,在一個售票的時間段后,最終舉辦方需要確定售票數量,這就是一個長事務。

然鵝,對于長事務來說總不能一直鎖住對應數據吧?

為了解決這個問題,假設一個長事務:T,

可以被拆分成許多相互獨立的子事務(subtransaction):t_1 ~ t_n。

以上述押尾桑的表演為例,每個 t 就是一筆售票記錄。

假如每次購票都一次成功,且沒有退票的話,整個流程就如下圖一般被正常地執行。

那假如有某次購票失敗了怎么辦?

3.3.3. Saga 的特殊條件

A LLT is a saga if it can be written as a sequence of transactions that can be interleaved with other transactions.
Saga 就是能夠被寫成事務的序列,并且能夠在執行過程中被其他事務插入執行的長事務。

Saga 通過引入補償事務(Compensating Transaction)的概念,解決事務失敗的問題。

即任何一個 saga 中的子事務 t_i,都有一個補償事務 c_i 負責將其撤銷(undo)。

注意是撤銷該子事務,而不是回到子事務發生前的時間點。

根據以上邏輯,可以推出很簡單的公式:

  • Saga 如果全部執行成功那么子事務序列看起來像這樣:t_1, t_2, t_3, ..., t_n

  • Saga 如果執行全部失敗那么子事務序列看起來像這樣:t_1, t_2, t_3, ..., t_n, c_n, ..., c_1

注意到圖中的 c_4 其實并沒有必要,不過因為每次撤銷執行都應該是冪等(Idempotent)的,所以也不會出錯。

篇幅有限在此就不繼續深入介紹...

  • 推薦看看從分布式系統方面講 Saga Pattern 的視頻:GOTO 2015 ? Applying the Saga Pattern ? Caitie McCaffrey
  • MSDN 的文章:A Saga on Sagas

3.4. 響應式編程(Reactive Programming)[[15]]

redux-saga 中間件基于 Sagas 的理論,通過監聽 action,生成對應的各種子 saga(子事務)解決了復雜異步問題。

而接下來要介紹的 redux-observable 中間件背后的理論是響應式編程(Reactive Programming)。

In computing, reactive programming is a programming paradigm oriented around data flows and the propagation of change.

簡單來說,響應式編程是針對異步數據流的編程并且認為:萬物皆流(Everything is Stream)。

流(Stream)就是隨著時間的流逝而發生的一系列事件。

例如點擊事件的示意圖就是這樣。

用字符表示【上上下下左右左右BABA】可以像這樣。(注意順序是從左往右)

--上----上-下---下----左---右-B--A-B--A---X-|->上, 下, 左, 右, B, A 是數據流發射的值 X 是數據流發射的錯誤 | 是完成信號 ---> 是時間線

那么我們要根據一個點擊流來計算點擊次數的話可以這樣。(一般響應式編程庫都會提供許多輔助方法如 map、filter、scan 等)

clickStream: ---c----c--c----c------c-->map(c becomes 1)---1----1--1----1------1-->scan(+) counterStream: ---1----2--3----4------5-->

如上所示,原始的 clickStream 經過 map 后產生了一個新的流(注意原始流不變),再對該流進行 scan(+) 的操作就生成了最終的 counterStream。

再來個栗子~,假設我們需要從點擊流中得到關于雙擊的流(250ms 以內),并且對于大于兩次的點擊也認為是雙擊。先想一想應該怎么用傳統的命令式、狀態式的方式來寫,然后再想想用流的思考方式又會是怎么樣的~。

這里我們用了以下輔助方法:

  • 節流:throttle(250ms),將原始流在 250ms 內的所有數據當作一次事件發射
  • 緩沖(不造翻譯成啥比較好):buffer,將 250ms 內收集的數據放入一個數據包裹中,然后發射這些包裹
  • 映射:map,這個不解釋
  • 過濾:filter,這個也不解釋

更多內容請繼續學習 RxJS。

3.5. redux-observable 中間件[[16]]

redux-observable 就是一個使用 RxJS 監聽每個 action 并將其變成可觀測流(observable stream)的中間件。

其中最核心的概念叫做 epic,就是一個監聽流上 action 的函數,這個函數在接收 action 并進行一些操作后可以再返回新的 action。

At the highest level, epics are “actions in, actions out”

redux-observable 通過在后臺執行 .subscribe(store.dispatch) 實現監聽。

Epic 像 Saga 一樣也是 Long Lived,即在應用初始化時啟動,持續運行到應用關閉。雖然 redux-observable 是一個中間件,但是類似于 redux-saga,可以想象它就像新開的進/線程,監聽著 action。

在這個運行流程中,epic 不像 thunk 一樣攔截 action,或阻止、改變任何原本 redux 的生命周期的其他東西。這意味著每個 dispatch 的 action 總會經過 reducer 處理,實際上在 epic 監聽到 action 前,action 已經被 reducer 處理過了。

所以 epic 的功能就是監聽所有的 action,過濾出需要被監聽的部分,對其執行一些帶副作用的異步操作,然后根據你的需要可以再發射一些新的 action。

舉個自動保存的栗子,界面上有一個輸入框,每次用戶輸入了數據后,去抖動后進行自動保存,并在向服務器發送請求的過程中顯示正在保存的 UI,最后顯示成功或失敗的 UI。

使用 redux-observable 中間件編寫代碼,可以僅用十幾行關鍵代碼就實現上述功能。

import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/dom/ajax'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/startWith';import {isSaving, savingSuccess, savingError, } from '../actions/autosave-actions.js';const saveField = (action$) => // 一般在變量后面加 $ 表示是個 streamaction$.ofType('SAVE_FIELD') // 使用 ofType 監聽 'SAVE_FIELD' action.debounceTime(500) // 防抖動// 即 map + mergeAll 因為異步導致 map 后有多個流需要 merge.mergeMap(({ payload }) =>Observable.ajax({ // 發起請求method: 'PATCH',url: payload.url,body: JSON.stringify(payload),}).map(res => savingSuccess(res)) // 發出成功的 action.catch(err => Observable.of(savingError(err))) // 捕捉錯誤并發出 action.startWith(isSaving()) // 發出請求開始的 action);export default saveField;

篇幅有限在此就不繼續深入介紹...

  • 關于 redux-observable 的前世今生推薦看看 Netfix 工程師的這個視頻:Netflix JavaScript Talks - RxJS + Redux + React = Amazing!
  • 如果覺得看視頻聽英語麻煩的話知乎有人翻譯了...

    • RxJS + Redux + React = Amazing!(譯一)
    • RxJS + Redux + React = Amazing!(譯二)

四、總結

本文從為 Redux 應用添加日志功能(記錄每一次的 dispatch)入手,引出 redux 的中間件(middleware)的概念和實現方法。

接著從最簡單的 setTimeout 的異步操作開始,通過對比各種實現方法引出 redux 最基礎的異步中間件 redux-thunk。

針對 redux-thunk 使用時模版代碼過多的問題,又介紹了用于優化的 redux-promise 和 redux-promise-middleware 兩款中間件。

由于本質上以上中間件都是基于 thunk 的機制來解決異步問題,所以不可避免地將維護異步狀態的責任推給了開發者,并且也因為難以測試的原因。在復雜的異步場景下使用起來難免力不從心,容易出現 bug。

所以還簡單介紹了一下將處理副作用的步驟放到 reducer 中并通過 Effect 進行解決的 redux-loop 中間件。然鵝因為其無法使用官方 combineReducers 的原因而無法被納入 redux 核心代碼中。

此外社區根據 Saga 的概念,利用 ES6 的 generator 實現了 redux-saga 中間件。雖然通過 saga 函數將業務代碼分離,并且可以用同步的方式流程清晰地編寫異步代碼,但是較多的新概念和 generator 的語法可能讓部分開發者望而卻步。

同樣是基于觀察者模式,通過監聽 action 來處理異步操作的 redux-observable 中間件,背后的思想是響應式編程(Reactive Programming)。類似于 saga,該中間件提出了 epic 的概念來處理副作用。即監聽 action 流,一旦監聽到目標 action,就處理相關副作用,并且還可以在處理后再發射新的 action,繼續進行處理。盡管在處理異步流程時同樣十分方便,但對于開發者的要求同樣很高,需要開發者學習關于函數式的相關理論。

五、參考資料

  • Redux 英文原版文檔
  • Redux 中文文檔
  • Dan Abramov - how to dispatch a redux action with a timeout
  • 阮一峰 - Redux 入門教程(二):中間件與異步操作
  • Redux 莞式教程
  • redux middleware 詳解
  • Thunk 函數的含義和用法
  • Redux異步方案選型
  • Sebastien Lorber - how to dispatch a redux action with a timeout
  • Sagas 論文
  • Pros/cons of using redux-saga with ES6 generators vs redux-thunk with ES7 async/await
  • Redux-saga 英文文檔
  • Redux-saga 中文文檔
  • Saga Pattern 在前端的應用
  • The introduction to Reactive Programming you've been missing
  • Epic Middleware in Redux
  • 以上 to be continued...

    總結

    以上是生活随笔為你收集整理的Redux 进阶 - react 全家桶学习笔记(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    丁香久久久| 免费观看十分钟 | 天天操天天添 | 国产精品综合在线 | 99精品福利| 欧美性成人 | 国产视频69 | 日韩av黄| 中文字幕国产精品一区二区 | 国产日韩精品一区二区三区在线 | 欧美99精品 | 国产精品免费观看久久 | 99久久精品免费看 | 亚洲精品午夜视频 | 射综合网 | 亚洲mv大片欧洲mv大片免费 | 欧美精品一区二区三区一线天视频 | 在线观看免费日韩 | 国产精品久久久久av福利动漫 | 国产精品中文久久久久久久 | 五月激情久久 | 亚洲美女久久 | 久久久午夜精品理论片中文字幕 | 日本一区二区免费在线观看 | 天天射天天做 | 欧美亚洲另类在线视频 | 91精品对白一区国产伦 | 日本少妇视频 | 美女精品久久久 | 久久精品99国产精品酒店日本 | 九九热在线播放 | 免费视频成人 | 久久97精品 | 亚洲黄色免费网站 | 日韩av高潮| 国产色爽 | 美女久久久| 久久免费视频这里只有精品 | 日韩av在线资源 | 97国产大学生情侣白嫩酒店 | 在线探花| 亚洲精品国产综合久久 | av看片在线观看 | 成年人黄色免费视频 | 色婷婷亚洲| 成人黄色电影在线 | 中文字幕制服丝袜av久久 | 天天综合精品 | 久久久久久久免费 | 麻豆影音先锋 | 视频在线在亚洲 | 激情久久综合网 | 国产精品少妇 | 久久99在线| 97色婷婷成人综合在线观看 | 中文字幕在线免费观看视频 | 日韩精品视频免费在线观看 | av黄色在线播放 | 久久精品一区二区国产 | 91九色丨porny丨丰满6 | 四虎在线免费 | 国产精品麻豆99久久久久久 | 亚洲区视频在线 | 在线小视频 | 99在线免费观看视频 | 手机成人免费视频 | 久久人91精品久久久久久不卡 | 久久影视一区二区 | 色婷婷97| 中文字幕在线专区 | 色天天中文 | 亚洲精品在线国产 | 欧美性极品xxxx娇小 | 九色精品免费永久在线 | 日韩av午夜在线观看 | 六月丁香在线视频 | 亚洲午夜av电影 | 国产在线探花 | 在线视频日韩一区 | 成人超碰在线 | 久在线观看视频 | 99国产在线观看 | 久草色在线观看 | 中文字幕视频一区 | 久久字幕 | 中文在线 | 黄色精品网站 | 欧美久久久久久久久中文字幕 | 久久久久久久久久久久av | 97视频人人免费看 | 国产综合小视频 | 人人爽人人澡人人添人人人人 | 久久伊人八月婷婷综合激情 | 欧美成人亚洲 | 欧美日韩中文字幕综合视频 | 五月综合色 | 婷婷丁香六月天 | 欧美最猛性xxxxx(亚洲精品) | 日韩精品久久久久久中文字幕8 | 国产免费视频一区二区裸体 | 玖玖综合网 | 精品一区二区av | 久久综合九色综合97婷婷女人 | 国产精品乱码高清在线看 | 狠狠躁18三区二区一区ai明星 | a久久免费视频 | 国产精品一区二区在线免费观看 | 精品一区二区精品 | 国产香蕉av | 国产久草在线 | 亚洲成人国产精品 | www.夜色321.com | 亚洲精品乱码久久久久久久久久 | 天天插狠狠插 | 欧美久久久一区二区三区 | 亚洲国产影院 | 人人操日日干 | 人人看看人人 | 毛片美女网站 | 91最新中文字幕 | 亚洲香蕉在线观看 | 国产黄色片免费 | 狠狠干天天射 | 亚洲va欧美va国产va黑人 | 国产精品岛国久久久久久久久红粉 | 99久久久久久国产精品 | 五月天天色 | 五月婷婷综合在线 | 国产精品黄色 | 中文在线 | 国产亚洲91 | 国产网站在线免费观看 | 久久综合久久八八 | 成人在线黄色电影 | 69国产成人综合久久精品欧美 | 免费观看av | 婷婷久久婷婷 | 日本色小说视频 | 精品一区二区日韩 | 久久精品2| 国产精品毛片一区视频播 | 91一区啪爱嗯打偷拍欧美 | 久久免费精彩视频 | 国产一级电影网 | 99热在线观看 | 国产精品视频久久 | 精品一区在线 | 一级理论片在线观看 | 91精品夜夜| 99久久影院 | 香蕉精品视频在线观看 | 黄网站污 | 一区二区三区久久 | 91福利免费 | 97超碰国产精品女人人人爽 | 91在线操 | 深爱婷婷网 | 一级黄毛片 | 国产午夜在线观看视频 | 99久久精品国产亚洲 | 日韩激情小视频 | 国产精品麻豆视频 | 一级免费片 | 国产综合在线视频 | 久久免费视频在线 | 2021国产在线 | 国产a级精品 | 欧美 日韩 国产 成人 在线 | 日日夜夜91 | 美女网站黄在线观看 | 精品国产乱码久久久久久1区2匹 | 免费看三级黄色片 | 国产中文欧美日韩在线 | 日本精品va在线观看 | 国产日韩欧美网站 | 久久女教师 | 亚洲视频综合在线 | 永久免费的啪啪网站免费观看浪潮 | 久久99精品久久久久久清纯直播 | 免费观看完整版无人区 | 精品国产一区二区三区四区在线观看 | 91视频在线| 欧美在线视频一区二区三区 | 亚洲精品在线观看视频 | 天天操操操操操操 | 久久一区二区三区国产精品 | 91亚洲精品国产 | 亚洲天天草| 五月黄色 | 亚洲a成人v| 黄色在线观看免费 | 日韩天天干| 国产美女视频免费 | 99久久夜色精品国产亚洲 | 亚洲专区免费观看 | 国偷自产中文字幕亚洲手机在线 | 精品久久久久久久久久久久 | 日本久久电影网 | 精品视频在线观看 | 色香蕉网| 麻豆传媒视频观看 | 久久你懂的 | 日韩国产精品一区 | 国产黑丝一区二区 | 精品96久久久久久中文字幕无 | 国产精品九九视频 | 97香蕉久久超级碰碰高清版 | 久章草在线观看 | 天天干.com | 国产精品99久久久久的智能播放 | 亚洲精品一区二区在线观看 | 九九热1 | 天天爽天天碰狠狠添 | 亚洲黄色一级大片 | 最新高清无码专区 | 日本中文乱码卡一卡二新区 | 免费成人短视频 | 香蕉视频国产在线 | 国产伦理久久精品久久久久_ | 中国美女一级看片 | 欧美另类调教 | 毛片在线网 | 亚洲色图色 | 99热这里只有精品免费 | 亚洲精品成人网 | 色婷婷狠 | 久草网视频 | 国产丝袜制服在线 | 中文视频在线 | 中文字幕永久在线 | 成人在线免费视频 | 国产中文字幕在线免费观看 | 久久精品视频18 | 欧美日韩国产色综合一二三四 | 精品久久久久久国产91 | 成人黄色免费观看 | 成人毛片在线观看 | 天天操夜夜操天天射 | 99久久精品无码一区二区毛片 | 久久久精品成人 | 欧美福利视频 | 免费av的网站| 日韩一二区在线观看 | 日韩专区在线播放 | 久久精品久久久久电影 | 日韩精品一区二区三区第95 | 狠狠干综合网 | 国产精品毛片网 | 天天玩天天操天天射 | 精品视频在线免费 | 国产精品永久免费 | 欧美 日韩 国产 中文字幕 | 国产精品一区在线观看 | 欧美 日韩 国产 中文字幕 | 日韩中字在线观看 | 黄色一级大片免费看 | 亚洲国产精品传媒在线观看 | 精品国产理论 | 国产a网站 | 天天草综合 | 精品视频一区在线观看 | av网址aaa | 在线看v片| 天天干天天想 | 色婷婷国产精品一区在线观看 | 日韩综合在线观看 | 久草视频在线播放 | 国产伦精品一区二区三区… | 亚洲国产久 | 天天射天天添 | 久久毛片视频 | 九九有精品 | 日韩在线视频在线观看 | 日本中文字幕久久 | 婷婷久久综合九色综合 | 国产精品久久久久久吹潮天美传媒 | 久久影院亚洲 | 亚洲精品 在线视频 | 久久久99精品免费观看app | 97成人在线观看 | 久久久久亚洲国产精品 | 欧美日韩高清在线 | 在线视频婷婷 | 天堂av高清 | 欧美一级久久 | avlulu久久精品 | 精品久久久久免费极品大片 | 波多野结衣在线观看一区二区三区 | av亚洲产国偷v产偷v自拍小说 | 男女精品久久 | 久久伊人热 | 久久成人视屏 | 欧日韩在线 | 国产视频99 | 天天综合天天综合 | 伊人电影在线观看 | 99热国产在线| 日本久久久精品视频 | 毛片无卡免费无播放器 | 久久久视屏 | 日韩专区av | 免费福利在线视频 | 亚洲专区欧美专区 | 欧美 日韩精品 | 久久人人爽视频 | 亚洲国产中文字幕在线观看 | 国产精品久久久久久久av电影 | 日韩激情在线 | 一级黄色在线视频 | 天天做天天射 | 91系列在线| 99精品热 | 天天插夜夜操 | 天天综合网天天综合色 | 色综合天天综合在线视频 | 日韩欧美精品在线 | 国产在线观看黄 | www国产亚洲精品久久麻豆 | 国产精品视频999 | 中文在线8资源库 | 97人人超| 久久综合婷婷综合 | 黄网站app在线观看免费视频 | 97视频免费在线观看 | 99精品毛片 | 亚洲免费在线观看视频 | 91超在线 | 日韩电影在线观看一区 | 黄色片亚洲 | 午夜成人影视 | 亚洲综合色网站 | 国产精品久久久久久久av大片 | 日本久久中文字幕 | 天天操天天爱天天爽 | 99在线精品视频观看 | 亚洲精品在线观看视频 | 国产精品电影在线 | 久久视屏网 | 中文字幕一区二区三区久久 | 探花视频在线观看免费版 | 菠萝菠萝蜜在线播放 | 成人h电影在线观看 | 一级淫片在线观看 | 国产精品毛片久久蜜 | 免费在线观看一区二区三区 | 91网址在线观看 | 性色av香蕉一区二区 | 日日天天 | 奇米影视在线99精品 | 日日弄天天弄美女bbbb | 精品主播网红福利资源观看 | 精品1区2区| 999国产精品视频 | 天天射天天干天天插 | 日日操操操 | 深夜福利视频一区二区 | a午夜电影| 亚洲综合黄色 | 天天躁日日躁狠狠躁 | 久久久久久久精 | 午夜精品区 | 97在线观视频免费观看 | 久视频在线播放 | 麻豆果冻剧传媒在线播放 | 欧美日韩精品影院 | 成人中文字幕在线观看 | 久久伊人精品一区二区三区 | 青青河边草手机免费 | 一区二区视频电影在线观看 | 美女搞黄国产视频网站 | 中日韩三级视频 | 国产免费人人看 | 在线欧美中文字幕 | 亚洲视频,欧洲视频 | 中文字幕黄色网址 | 国产亚洲欧美一区 | 99久久婷婷国产综合精品 | 国产精品久久久久久五月尺 | 麻豆极品 | 免费视频资源 | 免费三级影片 | 2021av在线 | 三级黄色在线 | 丁香激情网| 99久久久久国产精品免费 | 成人h电影 | 色婷丁香| 日韩最新理论电影 | 黄a网| 国产视频精选在线 | 婷婷综合av | 久久99亚洲热视 | 国产视频2区 | 丁香六月在线观看 | av大全在线免费观看 | 亚洲欧美成人网 | 操老逼免费视频 | 欧美天堂久久 | 91麻豆文化传媒在线观看 | 久久久国产99久久国产一 | 国产精品久久久久久久久费观看 | ww视频在线观看 | 国产破处视频在线播放 | 天天天天爱天天躁 | 精品一区二区在线观看 | 国产精品毛片一区二区在线看 | 婷婷色av| 日韩大片免费观看 | 久久综合视频网 | 亚洲三级在线播放 | 丁香婷婷综合色啪 | 亚洲另类视频在线观看 | 91麻豆精品国产91久久久无需广告 | 99精品视频免费全部在线 | 亚洲精品一区中文字幕乱码 | 国产一区二区三区高清播放 | 欧美精品三级 | 夜色.com| a爱爱视频 | 黄色三级在线 | 一区二区三区在线不卡 | 午夜三级影院 | 国产精品久久久久毛片大屁完整版 | 久久久午夜电影 | 久久久久久久久电影 | 欧美一区二区三区特黄 | 一区二区三区中文字幕在线 | 天天综合入口 | 日韩成人在线一区二区 | 亚洲第一久久久 | 精品av网站 | 国产99久| 久久www免费人成看片高清 | 国产精品免费观看国产网曝瓜 | 婷婷中文字幕在线观看 | 国产精品99久久久久的智能播放 | 日韩在线国产 | 伊人婷婷色 | 二区视频在线观看 | 国产精品麻豆视频 | 国产成人黄色网址 | 亚在线播放中文视频 | 久久不色| 操操操com | 99精品视频在线观看视频 | 午夜在线免费观看视频 | 日韩在线视频网 | 91av小视频| 国产日韩在线播放 | 一本一本久久a久久精品综合小说 | 久久视屏网 | 国产成人一区二区在线观看 | 欧美性大战 | 色综合亚洲精品激情狠狠 | av网站免费线看精品 | 亚洲成av人片在线观看无 | av三区在线 | 日韩免费一二三区 | 激情综合狠狠 | 国产毛片久久 | 成人在线播放av | 亚洲一区久久 | 久久久久国产一区二区 | 久久999久久| 成人av影视| 中文字幕精品一区久久久久 | 亚洲精品国产欧美在线观看 | 国产拍揄自揄精品视频麻豆 | 亚洲丝袜一区 | 日本性生活一级片 | 久久久久久久久久久久电影 | 国产精品毛片一区二区在线 | 麻豆成人精品 | 国产尤物在线观看 | 欧美精品乱码久久久久久按摩 | 亚洲永久国产精品 | 欧美最猛性xxxxx免费 | av福利在线导航 | av片在线观看 | 高清视频一区二区三区 | 欧美色插 | 丰满少妇在线观看网站 | 国产精品福利无圣光在线一区 | 久久久久久国产精品 | 亚洲va欧美 | 国产综合在线视频 | 亚洲乱码在线 | 日韩手机在线 | 久草精品免费 | 国产综合婷婷 | 美女视频永久黄网站免费观看国产 | 日韩一区在线播放 | 欧美色黄| 久久国产剧场电影 | 五月婷婷色播 | www色com | 免费看国产曰批40分钟 | 欧美成人猛片 | 精品视频 | 在线免费观看欧美日韩 | 视频三区| 四虎精品成人免费网站 | 国产成人久久av | 日色在线视频 | 97av视频| 日韩videos| av高清一区二区三区 | 欧美另类重口 | 中文字幕视频网站 | 免费高清av在线看 | 亚洲精品mv在线观看 | 久久在线精品视频 | 在线欧美日韩 | 国产精品电影一区二区 | 激情影音 | 欧美高清视频不卡网 | 久久国产91 | 亚洲国产欧洲综合997久久, | 天海翼一区二区三区免费 | 成人三级av | 黄色av观看 | 国产理论一区二区三区 | 久久天天躁夜夜躁狠狠85麻豆 | 亚洲精品福利视频 | 五月激情天 | 国产精品1区2区 | 婷婷伊人综合亚洲综合网 | 国产理论片在线观看 | 国产精品乱码一区二区视频 | 免费观看黄色av | 五月综合色婷婷 | 在线观看视频97 | 日韩电影中文字幕在线观看 | 在线观看www视频 | 免费看短| 999国内精品永久免费视频 | 最近高清中文在线字幕在线观看 | 亚洲一区视频免费观看 | 天天天干| 黄色小说视频在线 | 免费观看成人网 | 亚洲一区二区精品视频 | 日本久久久精品视频 | 国产黄a三级三级三级三级三级 | 人人狠狠综合久久亚洲婷 | 亚洲精品在 | 久久a免费视频 | 91精品国产乱码久久桃 | 不卡的av | 成人蜜桃网 | avav片 | 国产精国产精品 | 综合色狠狠 | 91黄色在线视频 | 久久热首页 | 久草99 | 99精品在线看 | 日本爽妇网 | 成人91在线| 中文字幕美女免费在线 | 国产r级在线观看 | 亚洲夜夜网 | 在线播放91 | 美女免费黄网站 | 欧美日本国产在线观看 | 日韩网站在线观看 | 涩五月婷婷 | 五月天中文在线 | 久久免费资源 | 丝袜少妇在线 | 亚州av免费 | 麻花豆传媒mv在线观看 | 日韩成人免费在线 | 黄色资源在线 | 中文字幕在线字幕中文 | 久久国产精品系列 | av成人免费网站 | 久久久久久久久久网 | 在线免费观看视频a | 亚洲日韩精品欧美一区二区 | 超碰免费久久 | 免费黄a| 亚洲天天摸日日摸天天欢 | 九九视频这里只有精品 | 日日干精品 | 久久免费国产电影 | 免费在线观看av网站 | 欧美一级片在线观看视频 | 丁香影院在线 | 欧美成人基地 | 免费人成网ww44kk44 | 久久久久久久久久免费视频 | 国产精品不卡在线 | 97碰碰精品嫩模在线播放 | 手机看片99| 免费在线观看日韩视频 | av 一区 二区 久久 | 久久香蕉一区 | 国产精品色婷婷视频 | 国产日韩欧美在线观看视频 | 97操操操| 久久99这里只有精品 | 97超碰超碰久久福利超碰 | 91丨九色丨91啦蝌蚪老版 | 91尤物国产尤物福利在线播放 | 黄av免费| 99国产免费网址 | 久久99精品久久久久久清纯直播 | 深爱五月激情五月 | 久久艹在线 | 麻豆免费在线播放 | 日韩动漫免费观看高清完整版在线观看 | 91在线免费视频 | 伊人亚洲综合网 | 精品一二| 黄色成年 | 91在线免费公开视频 | 97超在线| 亚洲天堂首页 | 在线观看中文字幕一区二区 | 中文字幕在线观看视频一区 | 精品久久久久国产 | 国产精品网红福利 | 天天干天天操天天操 | 亚洲色图27p | 狠狠操操网| 日韩成人精品一区二区三区 | 国产美女视频 | 99国产在线视频 | 97视频在线观看成人 | 久久精品欧美一区二区三区麻豆 | 夜夜躁天天躁很躁波 | 操操操操网 | 亚洲观看黄色网 | 国产精品久久一区二区三区, | 亚洲香蕉视频 | 毛片美女网站 | 五月网婷婷 | a视频在线观看 | 九九交易行官网 | 中文字幕一区二区三 | 在线观看av不卡 | 亚洲免费成人 | av在线免费观看黄 | 99免在线观看免费视频高清 | 在线亚洲小视频 | 麻豆久久精品 | 黄色福利视频网站 | ww视频在线观看 | 麻豆视屏 | 久久成人资源 | 日本中文字幕视频 | 免费观看91视频 | 成人a级大片| 激情黄色一级片 | 91九色国产视频 | 国产精品久久久久久久久久99 | 亚洲精品美女在线 | 在线观看国产麻豆 | 毛片激情永久免费 | 中文字幕在线精品 | 久久草| 欧美一区二区在线看 | 四虎www.| 天天射天天射天天 | 国产精品一区二区三区在线免费观看 | 激情五月婷婷综合 | 99re国产 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 欧美日韩高清一区二区三区 | 久久久久免费精品视频 | 国产久草在线 | 亚洲美女精品视频 | 欧美国产精品久久久久久免费 | 91久久国产自产拍夜夜嗨 | 亚洲男女精品 | 五月色综合 | 日日日爽爽爽 | 91精品国产福利在线观看 | 99久久这里有精品 | 97超碰人人爱 | 国产精品嫩草影视久久久 | 日韩欧美精品在线观看视频 | 欧美视频在线观看免费网址 | 中文伊人| 人人插人人澡 | www.91成人 | 国产福利一区二区三区视频 | 激情综合五月婷婷 | 有码中文字幕 | 国产精品免费在线 | 国产精品网红福利 | 中文字幕在线观看视频免费 | 狠狠色丁香久久婷婷综合_中 | 久在线观看视频 | 日韩r级在线 | 91精品啪 | 狠狠色丁香 | 特级西西人体444是什么意思 | 国产一区二区三区高清播放 | 国产1区2区 | 日韩美女一级片 | 亚洲色图 校园春色 | 亚洲成人欧美 | 玖玖视频国产 | 国产精品免费久久 | 久久久久久久久久久久久国产精品 | 欧美精品久久久 | 激情xxxx| 亚洲成人精品 | 久久欧美综合 | 嫩草91影院| 精品久久网 | 黄色a级片在线观看 | 日韩在线观看a | 91在线日韩 | 亚洲精品88欧美一区二区 | 久草网在线视频 | 超级碰碰免费视频 | 一区二区三区四区五区六区 | 免费看久久 | 久久福利 | 日韩视频免费 | 免费观看的黄色 | 九九久久影视 | 91麻豆精品国产91久久久久久久久 | 欧美性大战| 五月婷网 | 久久网页| 国产成人精品999 | www.夜夜干.com| 色婷婷亚洲精品 | 亚洲人成网站精品片在线观看 | 日韩丝袜在线观看 | 狠狠躁日日躁夜夜躁av | 中文字幕在线影院 | 国产伦精品一区二区三区在线 | 五月婷婷导航 | 99视频久久 | 狠狠激情中文字幕 | 久久免费大片 | 欧美日韩免费在线视频 | 成人va在线观看 | 日韩视频1区 | 国产玖玖视频 | 日韩久久精品 | 亚洲 欧洲 国产 精品 | 98久久| av天天澡天天爽天天av | 精品美女在线观看 | 日本精品二区 | av在线精品 | 中文字幕在线不卡国产视频 | 女人高潮一级片 | 国产91勾搭技师精品 | 1024手机基地在线观看 | 91久久精品一区二区三区 | 亚洲成人av免费 | avhd高清在线谜片 | 国内视频1区 | 日本黄色片一区二区 | 日本中文字幕在线观看 | 国产不卡精品视频 | 在线国产91| 亚洲国产欧美在线人成大黄瓜 | 欧美性天天 | 亚州av免费 | 天天干天天摸 | 国产亚洲婷婷 | 亚洲欧美国产精品久久久久 | 欧美日韩二区在线 | 国产成人精品三级 | 国产精品一区二区精品视频免费看 | 国产一区二区在线免费播放 | 亚洲成av人片在线观看无 | 日本性视频 | 一区av在线播放 | av亚洲产国偷v产偷v自拍小说 | 日韩免费网站 | 久久精品亚洲国产 | 婷婷丁香在线 | 天天干天天弄 | 黄色免费观看网址 | www免费看片com | 97激情影院| 日韩国产精品久久久久久亚洲 | 97av在线 | 91成年人网站 | 在线观看免费av网站 | 911久久香蕉国产线看观看 | 欧美日韩大片在线观看 | 在线91av | 久热免费在线观看 | 日日干网| 久久一区二区三区日韩 | www.看片网站| 久久6精品 | 久久免费高清视频 | 美国人与动物xxxx | 久久国产a| 久久99在线视频 | 色成人亚洲网 | 美女免费视频观看网站 | 91久久国产自产拍夜夜嗨 | 色大片免费看 | 久久在线一区 | 午夜性色 | 又黄又刺激视频 | 永久中文字幕 | 国产高清视频在线播放一区 | 激情av在线播放 | 亚洲国产69 | av五月婷婷 | 我要色综合天天 | 啪啪凸凸 | 福利视频导航网址 | www.五月激情.com | 成人av在线播放网站 | 天天天操天天天干 | 久久精品www人人爽人人 | 色婷婷国产精品一区在线观看 | 玖玖在线精品 | 成人99免费视频 | 久草免费在线 | 亚洲91精品在线观看 | 天天色天天操天天爽 | 97香蕉久久超级碰碰高清版 | 欧美天堂久久 | 香蕉久久久久 | 国产 日韩 欧美 自拍 | 伊人六月 | 黄色成品视频 | 国产精品粉嫩 | 成人黄色av网站 | 欧美成人理伦片 | 手机av观看| 国产视频第二页 | 亚洲精品久久视频 | 亚洲精品午夜aaa久久久 | 成年人视频免费在线 | 国产人在线成免费视频 | 午夜免费福利视频 | 久久97久久97精品免视看 | 免费高清在线观看成人 | 欧美 亚洲 另类 激情 另类 | 亚洲精品国产精品乱码不99热 | 国产又粗又硬又爽的视频 | 97av视频在线观看 | 国产免费av一区二区三区 | 欧美激情综合网 | 97超碰精品 | 在线观看免费福利 | 国产色黄网站 | 日韩精品免费一线在线观看 | 伊人影院得得 | 日韩三级在线观看 | 成人午夜剧场在线观看 | 亚洲精品999 | 久久精品久久久久电影 | 综合精品久久久 | 国产精品久久一 | 成人小视频在线免费观看 | 一 级 黄 色 片免费看的 | 国产美女在线精品免费观看 | av免费在线播放 | 99夜色 | 久久视频精品在线 | 久久涩视频 | 特黄色大片| 亚洲欧美婷婷六月色综合 | 2021国产视频 | 色狠狠婷婷 | 99精品在线直播 | 三级视频片 | 中文字幕一区二区三区视频 | 五月天婷婷视频 | www.久久色.com | 人人天天夜夜 | 婷婷综合伊人 | av免费网站 | 97精品超碰一区二区三区 | 国产麻豆视频免费观看 | 日本爱爱片 | 久久久国产一区二区 | 午夜精品久久久 | avove黑丝 | 国产精品久久久久久一二三四五 | 日韩欧美在线影院 | 久久国产影院 | 国产精品午夜免费福利视频 | 日韩二区在线观看 | 久久伊人精品天天 | 免费看污片 | 亚洲精品激情 | 色狠狠综合| 国产无吗一区二区三区在线欢 | 亚洲欧美视频在线播放 | av国产网站 | 在线你懂 | av电影免费观看 | 日韩免费看 | 在线v片| 日韩r级电影在线观看 | 久久99久久99精品免观看粉嫩 | 丁香五月缴情综合网 | 久精品视频在线观看 | 亚洲一级片在线看 | 天天爱天天射天天干天天 | 国产成人黄色 | 92av视频| 欧美一区二区三区特黄 | 九九久久电影 | 欧美一区二区在线刺激视频 | 久草网视频在线观看 | 中文国产在线观看 | 欧美巨大荫蒂茸毛毛人妖 | 99久久精品久久亚洲精品 | 欧美成人高清 | 亚洲精品视频一 | 激情综合色综合久久综合 | av丝袜天堂 | 亚洲精品字幕在线 | 国产婷婷 | av电影免费在线播放 | 超碰在线人人草 | 久草视频在线资源站 | bayu135国产精品视频 | 久久伊人婷婷 | 91精品视频一区二区三区 | 日本在线观看视频一区 | 正在播放久久 | 九九免费在线观看 | 99精品在线观看 | 国产真实精品久久二三区 | 国产乱码精品一区二区蜜臀 | 五月天久久久久久 | 亚洲婷婷在线视频 | 色网站在线免费 | 久久伦理| 亚洲精品一区二区三区四区高清 | 国产精在线 | av福利网址导航 | 久久er99热精品一区二区三区 | 国产视频1区2区3区 久久夜视频 | 成年人电影免费看 | 五月天av在线 | 国产美女免费观看 | av高清不卡 | 亚洲视频999 | 日韩高清一区 | 四虎在线观看视频 | 色播五月婷婷 | 国产亚洲精品久久久久久电影 | 激情五月色播五月 | 日韩精品三区四区 | av福利免费| 欧美日韩一级在线 | 午夜精品成人一区二区三区 | 中文字幕在线久一本久 | 中文字幕亚洲五码 | 午夜在线看片 | 91精品国产综合久久婷婷香蕉 | 久久99精品久久久久久清纯直播 | 日韩亚洲在线视频 | 日韩免费观看视频 | 久草国产视频 | 在线 欧美 日韩 | 91成人天堂久久成人 | 在线观看 国产 | 中文字幕一区二区三区四区久久 | 插综合网 | 91在线免费播放视频 | 久久69精品久久久久久久电影好 | 大型av综合网站 | 不卡电影一区二区三区 | 久久综合五月天 | 国模精品一区二区三区 | 色综合久久天天 | 干天天 | 亚洲黄色小说网址 | 亚洲激精日韩激精欧美精品 | 国产精品久久久久久欧美 | 国产成人一级电影 | 免费一级片观看 | 中文字幕黄色av | 国产在线观看你懂得 | 最近能播放的中文字幕 | 久久人人爽人人人人片 | 国产色综合天天综合网 | 国产精品成人一区二区三区吃奶 | 视频成人永久免费视频 | 精品一区二区三区久久久 | 中文字幕久久精品亚洲乱码 | 久久久久久久久爱 | 在线国产专区 | 五月香视频在线观看 | 欧美在线视频一区二区 | 成人黄色小说视频 |