亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 網站 > WEB開發 > 正文

Reflux原理與源碼詳解

2024-04-27 15:10:47
字體:
來源:轉載
供稿:網友

深度好文,本文轉載至:https://yq.aliyun.com/articles/61068

一、看前必讀

Reflux是Flux模式的一種具體實現。本文從一開始就分別介紹了Flux模式和Reflux的設計原理。之后,又對源碼進行深入剖析,將Reflux拆分成發布者和訂閱者的公共方法、Action和Store的實現、發布者隊列和View的設計等四個方面,并逐一解讀。

Flux模式介紹 Flux是Facebook提出的一種構建web應用的模式,用以幫助開發者快速整合React中的視圖組件。在整個流程中,數據從應用上層到底層,從父組件到子組件,單向流動 (unidirectional data flow)。它由Dispacther、Store、View三個主要部分構成??聪旅孢@張圖

╔═════════╗ ╔════════════╗ ╔═══════╗ ╔══════╗ ║ Action ║──────>║ Dispatcher ║──────>║ Store ║──────>║ View ║ ╚═════════╝ ╚════════════╝ ╚═══════╝ ╚══════╝ ^ ╔════════╗ │ └────────── ║ Action ║ ──────────┘ ╚════════╝ 通過這張圖,我們可以大概的了解什么是Flux模式。

Action收集了視圖變更的行為,比如用戶點擊了按鈕、需要定時發送的請求,然后通知Dispatcher

Dispatcher是一個單例,是一個根據不同Action,觸發對應的回調,維護Store

Store是一個數據中心,只有Store的變化才能直接引發View的變化

Action一直處于就緒狀態,以上三步周而復始

這種設計雖然提高了Store管理的復雜度,但能夠使得數據狀態變得穩定、可預測。由于Flux不是本文的重點,此處有簡化,需要了解更多的話,請訪問官網的Flux介紹。

二、Reflux原理分析

Reflux是Flux模式的一種實現。不過略有區別。

╔═════════╗ ╔════════╗ ╔═══════╗ ║ Action ║──────>║ Store ║──────>║ View ║ ╚═════════╝ ╚════════╝ ╚═══════╝ ^ │ └─────────────────────────────────┘

Reflux實現了單向數據流,也實現了Flux中提及的Action和Store。它將Action和Dispatcher合并到了一起。Dispatcher不再是一個全局的單例,大大的降低了編碼復雜度和維護的難度和復雜度。一個Action就是一個Dipatcher,可以直接引發Store的變化。Store可以監聽Action的變化。此外,如果有Store互相依賴的情況,那么Store可以直接監聽Store。

說到這里,聰明的你看到我說到“監聽”兩個字,肯定就大概猜到Reflux的代碼大概是怎么寫的。沒錯,Reflux這種設計,就是典型的訂閱發布模式。

在Reflux中,每一個Action都是一個發布者Publisher,View是一個訂閱者Listener。而Store比較特殊,它監聽Action的變化,并引發View的改變,所以它既是一個發布者,又是一個訂閱者。

三、Reflux源碼解讀

Reflux的核心代碼都在reflux-core這個庫文件里面,我們可以通過npm install reflux-core下載到本地。入口文件index.js和其他模塊,都在lib文件夾里面。index.js引入了lib下面的大部分文件,并將文件對應的方法掛載在Reflux這個變量下面。大概分成下面幾類:

Reflux的版本信息和公共方法 發布者和訂閱者的公共方法 創建Action和Store Reflux的發布者隊列 后面三塊是Reflux的實現核心,我們后面依次會講到。

在這些模塊中,并沒有涉及到View,說明Relfux是一種純粹的Flux思想的實現方式,可以脫離React與其他的框架一起使用。View的設計,都在refluxjs這個庫里。我們可以通過npm install refluxjs下載代碼到本地。

發布者和訂閱者的公共方法

Reflux中的Action、Store、View其實只有兩種角色,一個是發布者Publisher,一個是訂閱者Listener。于是,Reflux將這兩種角色的公共方法抽象成了兩個模塊PublisherMethods.js和ListenerMethods.js。我們分別來看:

PublisherMethods

這個文件保存了發布者的公共方法,也就是Action和Store作為發布者都有的方法。文件的返回值是一個如下的對象:

module.exports = { // 觸發之前的回調, 在shouldEmit之前執行 PReEmit: function(){...}, // 是否能夠觸發,返回boolean值 shouldEmit: function(){...}, // 設置監聽事件,觸發后執行 listen: function(){...}, // 當shouldEmit的執行結果為true時,立即執行 trigger: function(){...}, // 當shouldEmit的執行結果為true, 盡快執行 triggerAsync: function(){...}, // 為trigger包裹一層函數defer函數 deferWith: function(){...}}

preEmit和shouldEmit

在trigger執行之前,首先會先執行preEmit和shouldEmit回調。preEmit用于修改發布者傳過來的參數,并將返回值會傳給shouldEmit。由shouldEmit的返回值true或者false判斷是否觸發。

listen和trigger listen方法和trigger方法是配套的。先看listen,里面有兩行比較關鍵:

this.emitter.addListener(this.eventLabel, eventHandler);... me.emitter.removeListener(me.eventLabel, eventHandler);...

我們在trigger這個方法中,看到代碼

...this.emitter.emit(this.eventLabel, args);...

而this.emitter,在后面我們會看到,他就是EventEmitter的一個實例。EventEmitter這個庫,是用作對象注冊和觸發相關事件的。所以listen和trigger兩個方法的意思已經很清楚了。就是listen方法的作用就是注冊監聽,返回一個可以解除注冊事件的函數。而trigger則是觸發事件的方法。

trigger和triggerAsync

這兩個方法比較有意思,一個是立即執行,一個是盡快執行。什么意思呢。我們看util.js中的對應代碼:

_.nextTick(function () { me.trigger.apply(me, args);});

而這個所謂的_.nextTick實際上是這個:

setTimeout(callback, 0);

那么實際上就是:

triggerAsync: function(){ let me = this; let args = arugments; setTimeout(function(){ me.trigger.apply(me, args) }, 0);}

triggerAsync的設計,主要是為了解決一些異步操作導致的問題。這里我用Uxcore舉個例子。在Uxcore的Form有個重置所有的FormField的方法叫resetValue。它的實現原理是這樣的:Form本身保存了一份原始值,調用resetValues的時候,會把這份原始值異步賦給各個FormField。所以,如果在下面這個場景中,繼續調用trigger,就不會獲得預期效果。要改用triggerAsync。

// User.Search用來搜索符合條件的員工let User = Reflux.createActions({ Search: { children: ['reset', 'do', ...] }});// 調用resetValues,清空搜索表單的值User.Search.reset();// 用初始值搜索一次// 下面這個不會取得預期效果// 這個與User.Search.do()效果相同User.Search.do.trigger();// 要用這個// User.Search.do.triggerAsync();

deferWith deferWith重寫了trigger方法。把之前的trigger保存到變量oldTrigger中,并將其作為第一個參數傳遞給deferWith的第一個參數callback,剩下的參數依次傳遞。舉個例子,如果我們執行的是

deferWith(fn, a, b, c)

那么,trigger方法就會變成

function(){ fn.apply(this, [oldTrigger, a, b, c]); }

ListenMethods

這個文件保存了訂閱者的公共方法,也就是Store和View作為訂閱者都有的方法。文件的返回值是一個如下的對象:

module.exports = { // 這個是給validateListening使用的工具方法 hasListener: function(){...}, // 多次調用listenTo, 一次性設置多個監聽 listenToMany: function(){...},, // 這個是給listenTo使用的工具方法 // 校驗監聽函數是否是合法, 比如 // 是否監聽自己,是否通過函數監聽,是否循環監聽 validateListening: function(){...}, // 設置監聽函數 listenTo: function(){...}, // 停止監聽 stopListeningTo: function(){...}, // 停止所有監聽 stopListeningToAll: function(){...}, // 這個是給listenTo使用的工具方法 // 執行發布者的getInitialState方法 // 并以其返回值為參數,執行一個默認的回調defaultCallback fetchInitialState: function(){...}, //下面這四個方法,就是Reflux中發布者隊列了,我們后面來說 joinTrailing: maker("last"), joinLeading: maker("first"), joinConcat: maker("all"), joinStrict: maker("strict")}

這個文件有一個核心方法,就是listenTo。它連接了發布者和訂閱者。我們看源代碼:

listenTo: function(listenable, callback, defaultCallback){ ... //訂閱者的數組,保存了所有的訂閱者信息 subs = this.subscriptions = this.subscriptions || []; ... subscriptionobj = { // unsubscriber是一個取消監聽的函數, // 也是stopListeningTo能夠取消監聽的原因 stop: unsubscriber, // listenable指的是發布者,就是誰被監聽 listenable: listenable }; // 把subscriptionobj對象push進訂閱者數組里 subs.push(subscriptionobj); return subscriptionobj;}

創建Action和Store

創建Action的模塊

Action相關的方法被放在ActionMethods.js和createAction.js兩個文件中。另外,index.js文件也定義了同時創建多個Action的createActions方法。

ActionMethods

ActionMethods這個模塊代碼只有最簡單的一行

module.exports = {};

但是作用可不簡單,它給所有的Action設置了公共的方法,可以在你需要的時候隨時調用。ActionMethods在index.js中被直接掛在了Reflux下面。所以你可以直接使用。

比如說我們定義一個

Reflux.ActionMethods.alert = function (i) { alert(i);};var showMsg = Reflux.createAction();

那么你可以這么使用:

showMsg.alert('Hello Reflux!');

這樣就會直接彈出一個alert框。非常粗暴,也非常實用。

createAction

我們知道createAction用法有這幾個

// 空參數創建var TodoAction1 = Reflux.createAction();// 立即執行還是盡快執行var TodoAction2 = Reflux.createAction({ sync: true});// 是否是異步的Actionvar TodoAction3 = Reflux.createAction({ asyncResult: true});// 設置子方法var TodoAction4 = Reflux.createAction({ children: ['success', 'warning']});// TodoAction5是一個有多個Action的數組var TodoAction5 = Reflux.createAction(['create', 'retrieve', {update: {sync: true}}]);...

我們再跟一下源碼,看是怎么做的。createAction方法一開始就有兩個for循環,用以檢驗要Action的名稱合法性,不能與Reflux.ActionMethods中的方法重名,也不能與已定義過的Action重名,我們假設叫做TodoAction。

源碼如下:

var createAction = function createAction(definition) { ... // 省略校驗的代碼 ... // 定義子Action definition.children = definition.children || []; // 如果是一個異步的操作,那么就額外給其加上兩個子Action,completed和failed if (definition.asyncResult) { definition.children = definition.children.concat(["completed", "failed"]); } // 這里是是個遞歸,生成所有的子Action // 將所有的children遍歷一遍,為每一個都執行createAction方法 var i = 0, childActions = {}; for (; i < definition.children.length; i++) { var name = definition.children[i]; childActions[name] = createAction(name); } // 將發布者的公共方法,Action公共的方法和當前要創建的TodoAction的配置merge到一起 var context = _.extend({ eventLabel: "action", emitter: new _.EventEmitter(), _isAction: true }, PublisherMethods, ActionMethods, definition); // 設置如果把當前要創建的Action TodoAction當做函數直接執行的策略 // 如果sync為true,那么執行TodoAction()就相當于執行TodoAction.trigger() // 反之,就相當于執行TodoAction.triggerAsync() var functor = function functor() { var triggerType = functor.sync ? "trigger" : "triggerAsync"; return functor[triggerType].apply(functor, arguments); }; //繼續合并 _.extend(functor, childActions, context); //將生成的Action,保存進Keep.createdActions數組里面 Keep.createdActions.push(functor); return functor;}module.exports = createAction;

createActions

創建多個Action,我們一般有兩種用法:

// 參數是數組var TextActions1 = Reflux.createActions(['create', 'retrieve', 'update', 'delete']);// 參數是對象var TextActions2 = Reflux.createActions({ 'init': { sync: true }, 'destroy': { asyncResult: true }});

所以,index.js中的createActions,其實就是判斷參數是否是一個數組,如果是,就對每一個數組項都調用一次createAction方法。反之,就當成一個key-value型的對象處理。所有的key都作為Action的名稱,所有的value都作為對應Action的配置。

創建Store的模塊

Store相關的方法被放在StoreMethods.js和createStore.js兩個文件中。

StoreMethods

StoreMethods這個模塊與ActionMethods類似,代碼只有最簡單的一行

module.exports = {};

但是作用可不簡單,它給所有的Store設置了公共的方法。

createStore

createStore與createAction也很類似。createStore方法一開始也有兩個for循環,用以檢驗要Store的名稱合法性,不能與Reflux.StoreMethods中的方法重名,也不能與已定義過的Store重名。我們來看具體的代碼:

module.exports = function (definition) { var StoreMethods = require("./StoreMethods"), PublisherMethods = require("./PublisherMethods"), ListenerMethods = require("./ListenerMethods"); // 這里與createAction一樣,是校驗Store名稱的合法性 ... // 這里是Store的核心方法 function Store() { var i = 0, arr; // 同樣的 訂閱者數組 this.subscriptions = []; // 這就是我們之前在PublisherMethods中講過的emitter this.emitter = new _.EventEmitter(); ... // 如果有init方法,則執行 // 如果沒有用listenToMany設置監聽方法,那么就需要在init中設置listenTo了 if (this.init && _.isFunction(this.init)) { this.init(); } // 如果有訂閱的回調,則執行ListenMethods中的方法監聽 if (this.listenables) { arr = [].concat(this.listenables); for (; i < arr.length; i++) { this.listenToMany(arr[i]); } } } // 這里是核心的一步,給Store的原型上merge進訂閱者、發布者、Store的公共方法和當前創建的Store的配置 _.extend(Store.prototype, ListenerMethods, PublisherMethods, StoreMethods, definition); // 實例化Store var store = new Store(); // 把sotre放入一個公共的數據,方便統一管理 Keep.createdStores.push(store); return store;};

Reflux的發布者隊列

剛才在ListenMethods中,訂閱者可以訂閱多個發布者的消息,這些發布者形成了一個隊列。如果發布者隊列遇到插隊的問題怎么辦呢?舉個例子,S順序訂閱了A和B。如果執行完A(‘a’),B(‘b’)即將執行的時候,用戶插入了A(‘A’),。那么S怎樣處理A(‘a’)、A(‘A’)和B(‘b’)的執行結果呢?

Reflux提出了joinTrailing、joinLeading、joinConcat、joinStrict四種處理策略,分別對應了last、first、all、strict四種邏輯, 亦即,執行A(‘A’)->B(‘b’)、A(‘a’)->B(‘b’)、A(‘a’)->A(‘A’)->B(‘b’)、A(‘a’)執行后報錯。上一個的執行結果,會傳給下一個。

因為這個相對較少使用,我在這里以Action為發布者,Store為監聽者為例寫一段代碼,用以幫助理解。

var A = Reflux.createAction();var B = Reflux.createAction();var Store = Reflux.createStore({ init: function() { let me = this; // 這里要根據需要設置成不同的策略 me.joinStrict(A, B, me.trigger); }});Store.listen(function() { console.log('result:', JSON.stringify(arguments));});// 測試片段1//A('a');//A('A');//B('b');//B('B');// 測試片段2A('a');B('b');A('A');B('B');

在這段代碼中,把A和B形成了一個隊列。執行順序為A->B。對不同策略分別執行測試片段1和測試片段2。

joinStrict

測試片段1 Uncaught Error: Strict join failed because listener triggered twice. result: {“0”:[“a”],”1”:[“b”]} 測試片段2 result:{“0”:[“a”],”1”:[“b”]} result:{“0”:[“A”],”1”:[“B”]} 結論 A->B之間,如果插入了A,就會執行第一個A,同時拋出一個錯誤,停止執行。

joinLeading

測試片段1 result: {“0”:[“a”],”1”:[“b”]} 測試片段2 result: {“0”:[“a”],”1”:[“b”]} result: {“0”:[“A”],”1”:[“B”]}

結論 A->B之間,如果插入了A,就執行第一個A,跳過后面的。 第一個A的執行結果,作為參數傳遞給B。B依照這個邏輯,繼續執行。

joinTrailing

測試片段1 result: {“0”:[“A”],”1”:[“b”]} 測試片段2 result: {“0”:[“a”],”1”:[“b”]} result: {“0”:[“A”],”1”:[“B”]}

結論 A->B之間,如果插入了A,就執行后一個A,跳過前面的。 后一個A的執行結果,作為參數傳遞給B。B依照這個邏輯,繼續執行。

joinConcat

測試片段1 result: {“0”:[[“a”],[“A”]],”1”:[[“b”]]}

測試片段2 result: {“0”:[[“a”]],”1”:[[“b”]]} result: {“0”:[[“A”]],”1”:[[“B”]]}

結論 A->B之間,如果插入了A,就再執行一次A。 兩個A的執行結果,放到一個數組里面,作為參數都傳遞給B。B依照這個邏輯,繼續執行。

這里我們簡單做一個總結。

策略 邏輯 遇到插隊時 是否繼續執行 joinStrict strict 拋出錯誤 否 joinLeading first 執行第一個 是 joinTrailing last 執行后一個 是 joinConcat all 都會執行 是 這四種策略,都定義在joins.js文件里面。我們看一段核心代碼:

// 返回一個函數// 該函數根據不同的策略,確定不同的后面監聽函數的參數function newListener(i, join) { return function () { var callargs = slice.call(arguments); // 對應的監聽若果尚未被觸發,就根據相應的策略來確定該監聽的參數 if (join.listenablesEmitted[i]) { switch (join.strategy) { // 如果是strict的,則只能執行一次,拋出錯誤 case "strict": throw new Error("Strict join failed because listener triggered twice."); // 如果是last的,則監聽函數的參數就為該函數的參數 case "last": join.args[i] = callargs;break; // 如果是all的,則監聽函數的參數是之前執行過的所有監聽的返回值構成的數組 case "all": join.args[i].push(callargs); } } else { // 設置監聽已觸發 join.listenablesEmitted[i] = true; join.args[i] = join.strategy === "all" ? [callargs] : callargs; } // 所有的監聽都觸發后執行join.callback,并重置隊列 // 這里打個斷點,可以幫助我們更好的理解上面的示例代碼 emitIfAllListenablesEmitted(join); };}...

發布者隊列類似于Flux模式中的waitFor設計,具有非常廣泛的使用場景:

請求完一個接口后,繼續請求一個接口 新手引導 先出現loading提示,再請求接口,最后取消loading或者顯示loaded 一個的處理結果,需要等待另一個的處理結果 …

四、View的設計

我們前文分析過,View是一個訂閱者。那么View就要有ListenerMethods的所有方法。因為我們的View層是基于React框架的,那么訂閱和發布d的消息,應該在對應的生命周期里發生。源碼中也確實是這么實現的。

在實際使用中,我們一般通過mixins,將Reflux和React聯系在一起。這樣,Reflux就可以在React對應的生命周期執行對應的操作。下面依舊從refluxjs的入口文件src/index.js分析。index.js中,也給Reflux變量掛載了幾個方法。這幾個方法在設計上是比較雷同的,一般是分兩步。第一步,是在componentDidMount的時候,注冊監聽;第二步,則是在componentWillUnmount的時候,移除所有的監聽。我們分開來看。

ListenerMixin

ListenerMixin是View其他方法所共用的,類似ListenerMethods。

...module.exports = _.extend({ componentWillUnmount: ListenerMethods.stopListeningToAll}, ListenerMethods);

它返回一個merge了ListenerMethods的對象。這個對象明確要求,組件要卸載(移除)的時候取消所有注冊的監聽。

listenTo

listenTo方法將某個Store與組件的某個方法關聯起來。當Store變化時,就調用設置的回調callback。

...// 這里的三個參數實際上就是// 要監聽的 store// store 變化后要執行的回調 callback// initial 是計算完初始值后執行的回調(一般不需要)// 這個就是剛才fetchInitialState中說到的回調defaultCallbackmodule.exports = function(listenable,callback,initial){ return { ... componentDidMount: function() { ... // 通過 listenTo 注冊監聽 this.listenTo(listenable,callback,initial); }, ... // 通過 stopListeningToAll 取消所有監聽 componentWillUnmount: ListenerMethods.stopListeningToAll };};

listenTo方法的實現方式很簡單了,在組件加載完成的時候,注冊監聽,在組件要卸載的時候,取消監聽。

listenToMany

listenToMany與listenTo基本一樣。區別就是listenToMany調用了ListenerMethods的listenToMany,可以同時注冊多個監聽。

module.exports = function(listenables){ return { componentDidMount: function() { ... // 通過 listenToMany 注冊監聽 this.listenToMany(listenables); }, ... // 通過 stopListeningToAll 取消所有監聽 componentWillUnmount: ListenerMethods.stopListeningToAll };};

connect

connect方法可以將組件的某一部分state,與指定的Store上。當Store變化的時候,組件的state也同步更新。

// listenable 指的就是要監聽的store// key 則為與store綁定后,需要變化的state[key]的key// 也就是說,store變化后,state[key]也同步變化module.exports = function(listenable, key) { // 如果事件沒有key,則直接報錯 _.throwIf(typeof(key) === 'undefined', 'Reflux.connect() requires a key.'); return { // 獲取state初始值 // 因為是mixin到React中的,所以比React中的getInitialState要先執行 getInitialState: function() { ... }, componentDidMount: function() { var me = this; // 依然是給React 混入ListenerMethods的方法 _.extend(me, ListenerMethods); // 設置監聽 this.listenTo(listenable, function(v) { me.setState(_.object([key],[v])); }); }, // 這里其實就是取消所有的監聽 componentWillUnmount: ListenerMixin.componentWillUnmount };};

connectFilter

connectFilter與connect設計思路基本類似,只不過每次在state的值被被setState前,都會執行一個filterFunc函數來做處理。connectFilter的設計,既能夠幫助開發人員保護state不被污染,又能夠減少不必要的更新。

module.exports = function(listenable, key, filterFunc) { // 省略部分是校驗key值的合法性 … return { // 獲取state初始值 getInitialState: function() { … // 這里是與上一節的connect方法不同的地方 // 在返回state的之前,先執行filterFunc函數 var result = filterFunc.call(this, listenable.getInitialState()); … }, componentDidMount: function() { … this.listenTo(listenable, function(value) { // setState前先處理 var result = filterFunc.call(me, value); me.setState(_.object([key], [result])); }); }, // 取消所有的監聽 componentWillUnmount: ListenerMixin.componentWillUnmount }; };


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91色精品视频在线| 亚洲人精品午夜在线观看| 精品中文字幕久久久久久| 免费av在线一区| 日韩av网站在线| 国产精品久久久久久久久久久久久| 久久影视电视剧免费网站| 中文字幕亚洲一区二区三区五十路| 亚洲欧美中文在线视频| 久久久综合av| 麻豆国产精品va在线观看不卡| 欧美日韩亚洲一区二区| 成人有码在线播放| www.欧美视频| 尤物精品国产第一福利三区| 国产成人av在线播放| 欧美综合在线观看| 欧美精品一本久久男人的天堂| 热门国产精品亚洲第一区在线| 欧美国产欧美亚洲国产日韩mv天天看完整| 久久免费高清视频| 色妞在线综合亚洲欧美| 中文字幕亚洲欧美日韩高清| 亚洲欧美激情另类校园| 国产精品三级网站| 夜夜嗨av色一区二区不卡| 中文字幕日韩在线观看| 国产一区二区三区三区在线观看| 成人免费观看网址| 亚洲精品久久久久久久久| 一个人www欧美| 久久免费国产视频| 欧美一级大胆视频| 97av在线播放| 国产一区二区三区在线免费观看| 久久精品亚洲94久久精品| 久久久免费观看| 国产精品狼人色视频一区| 久久久国产精品免费| 亚洲欧美成人在线| 日韩精品视频在线观看网址| 日韩激情av在线免费观看| 一区二区三区高清国产| 亚洲国产精品va在线看黑人| 国产99久久久欧美黑人| 久久免费视频在线| 国产一区红桃视频| 日韩电影免费在线观看中文字幕| 国产视频精品一区二区三区| 欧美日韩成人在线观看| 亚洲四色影视在线观看| 一区二区三区在线播放欧美| 国产欧美一区二区三区四区| 亚洲精品视频在线播放| 午夜精品一区二区三区视频免费看| 亚洲一区二区久久| 亚洲性猛交xxxxwww| 欧美亚洲激情视频| 欧美日韩美女在线观看| 在线观看中文字幕亚洲| 亚洲精品中文字幕有码专区| 亚洲自拍偷拍网址| 欧美国产中文字幕| 国产91ⅴ在线精品免费观看| 亚洲欧洲国产伦综合| 欧美又大又硬又粗bbbbb| 国产91在线高潮白浆在线观看| 高清一区二区三区日本久| 国产精品免费观看在线| 国模精品视频一区二区三区| 高清一区二区三区四区五区| 国产一区二区欧美日韩| 91经典在线视频| 国产中文字幕亚洲| 久久久国产视频91| 欧美一级淫片aaaaaaa视频| 97精品久久久中文字幕免费| 91亚洲国产精品| 日韩经典一区二区三区| 国产在线精品播放| 91免费高清视频| 国内精品久久影院| 久久久精品视频成人| 91在线观看免费高清完整版在线观看| 丝袜亚洲另类欧美重口| 曰本色欧美视频在线| 国产精品盗摄久久久| 日韩精品视频在线观看免费| 欧美日韩综合视频网址| 51久久精品夜色国产麻豆| 美日韩精品视频免费看| 欧美在线观看网址综合| 国产精品美女免费视频| 狠狠躁夜夜躁人人爽天天天天97| 亚洲大尺度美女在线| 欧美自拍视频在线观看| 欧美综合在线观看| 精品国产自在精品国产浪潮| 91亚洲va在线va天堂va国| 精品综合久久久久久97| 日韩av在线免费播放| 在线国产精品视频| 欧美激情videoshd| 亚洲大尺度美女在线| 啪一啪鲁一鲁2019在线视频| 亚洲欧美中文字幕在线一区| 精品国产一区二区三区四区在线观看| 国产va免费精品高清在线观看| 亚洲精品videossex少妇| 中文字幕亚洲一区在线观看| 97在线观看视频| 中文字幕在线观看日韩| 久久青草精品视频免费观看| 久久精品亚洲94久久精品| 成人福利网站在线观看11| 欧美精品制服第一页| 91精品国产综合久久香蕉最新版| 川上优av一区二区线观看| 成人亚洲综合色就1024| 美日韩精品视频免费看| 国产一区二区av| 亚洲第一中文字幕| 欧美性生交xxxxx久久久| 欧美精品久久久久久久免费观看| 上原亚衣av一区二区三区| 欧美老少做受xxxx高潮| 91情侣偷在线精品国产| 国产91网红主播在线观看| 97成人精品区在线播放| 精品动漫一区二区| 91久久嫩草影院一区二区| 日韩成人免费视频| 亚洲一级免费视频| 麻豆乱码国产一区二区三区| 91国在线精品国内播放| 最新日韩中文字幕| 国产在线精品自拍| 亚洲第一偷拍网| 国产有码在线一区二区视频| 欧美裸体xxxx极品少妇| 日本欧美爱爱爱| 色综合亚洲精品激情狠狠| 久久久久国产精品免费网站| 97婷婷涩涩精品一区| 另类天堂视频在线观看| 国产福利精品av综合导导航| 福利一区视频在线观看| 国产99视频在线观看| 欧美激情中文字幕在线| 日韩人在线观看| 日韩国产在线播放| www.99久久热国产日韩欧美.com| 国产一区二区美女视频| 国产美女精品视频免费观看| 久久成人综合视频| 欧美丝袜一区二区三区| 中文字幕亚洲一区| 国产精品电影网| 欧美成人精品h版在线观看| 色yeye香蕉凹凸一区二区av| 国产99久久精品一区二区 夜夜躁日日躁| 欧美高清性猛交| 国模私拍视频一区| 精品视频在线播放色网色视频|