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

如何優雅的使用 React Context

開發 前端
回到 React Context? 工作原理來看,只要有消費者訂閱了該 Context?,在該 Context? 發生變化時就會觸達所有的消費者。也就是說整個工作流程都是以 Context? 為中心的,那只要把 Context? 拆分的粒度足夠小就不會帶來額外的渲染負擔。

在開始今天的文章之前,大家不妨先想一下觸發 React 組件 re-render 的原因有哪些,或者說什么時候 React 組件會發生 re-render 。

先說結論:

  • 狀態變化
  • 父組件 re-render
  • Context 變化
  • Hooks 變化

這里有個誤解:props 變化也會導致 re-render。其實不會的,props 的變化往上追溯是因為父組件的 state 變化導致父組件 re-render,從而引起了子組件的 re-render,與 props 是否變化無關的。只有那些使用了 React.memo 和 useMemo 的組件,props 的變化才會觸發組件的 re-render。

針對上述造成 re-render 的原因,又該通過怎樣的策略優化呢?感興趣的朋友可以看這篇文章:React re-renders guide: everything, all at once。

接下來開始我們今天的主題:如何優雅的使用 React Context。上面我們提到了 Context 的變化也會觸發組件的 re-render,那 React Context 又是怎么工作呢?先簡單介紹一下 Context 的工作原理。

Context 的工作原理

Context 是 React 提供的一種直接訪問祖先節點上的狀態的方法,從而避免了多級組件層層傳遞 props 的頻繁操作。

創建 Context

通過 React.createContext 創建 Context 對象

export function createContext(
  defaultValue
) {
  const context = {
    $$typeof: REACT_CONTEXT_TYPE,
    _currentValue: defaultValue, 
    _currentValue2: defaultValue, 
    _threadCount: 0,
    Provider: (null: any),
    Consumer: (null: any),
  };

  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };
  context.Consumer = context;
  return context;
}

React.createContext 的核心邏輯:

  1. 將初始值存儲在 context._currentValue
  2. 創建 Context.Provider 和 Context.Consumer 對應的 ReactElement 對象

在 fiber 樹渲染時,通過不同的 workInProgress.tag 處理 Context.Provider 和 Context.Consumer 類型的節點。

主要看下針對 Context.Provider 的處理邏輯:

function updateContextProvider(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  const providerType = workInProgress.type;
  const context = providerType._context;
  
  const newProps = workInProgress.pendingProps;
  const oldProps = workInProgress.memoizedProps;
  
  const newValue = newProps.value;

  pushProvider(workInProgress, context, newValue);

  if (oldProps !== null) {
    // 更新 context 的核心邏輯
  }

  const newChildren = newProps.children;
  reconcileChildren(current, workInProgress, newChildren, renderLanes);
  return workInProgress.child;
}

消費 Context

在 React 中提供了 3 種消費 Context 的方式

  1. 直接使用 Context.Consumer 組件(也就是上面 createContext 時創建的 Consumer)
  2. 類組件中,可以通過靜態屬性 contextType 消費 Context
  3. 函數組件中,可以通過 useContext 消費 Context

這三種方式內部都會調用 prepareToReadContext 和 readContext 處理 Context。prepareToReadContext 中主要是重置全局變量為readContext 做準備。

接下來主要看下readContext :

export function readContext<T>(
  context: ReactContext<T>,
  observedBits: void | number | boolean,
): T {
  const contextItem = {
    context: ((context: any): ReactContext<mixed>),
    observedBits: resolvedObservedBits,
    next: null,
  };

  if (lastContextDependency === null) {
    lastContextDependency = contextItem;
    currentlyRenderingFiber.dependencies = {
      lanes: NoLanes,
      firstContext: contextItem,
      responders: null,
    };
  } else {
    lastContextDependency = lastContextDependency.next = contextItem;
  }

  // 2. 返回 currentValue
  return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}

readContext的核心邏輯:

  1. 構建 contextItem 并添加到 workInProgress.dependencies 鏈表(contextItem 中保存了對當前 context 的引用,這樣在后續更新時,就可以判斷當前 fiber 是否依賴了 context ,從而判斷是否需要 re-render)
  2. 返回對應 context 的 _currentValue 值

更新 Context

當觸發 Context.Provider 的 re-render 時,重新走 updateContextProvider 中更新的邏輯:

function updateContextProvider(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  // ...
  // 更新邏輯
  if (oldProps !== null) {
      const oldValue = oldProps.value;
      if (is(oldValue, newValue)) {
        // 1. value 未發生變化時,直接走 bailout 邏輯
        if (
          oldProps.children === newProps.children &&
          !hasLegacyContextChanged()
        ) {
          return bailoutOnAlreadyFinishedWork(
            current,
            workInProgress,
            renderLanes,
          );
        }
      } else {
        // 2. value 變更時,走更新邏輯
        propagateContextChange(workInProgress, context, renderLanes);
      }
  //...
}

接下來看下 propagateContextChange (核心邏輯在 propagateContextChange_eager 中) 的邏輯:

function propagateContextChange_eager < T > (
    workInProgress: Fiber,
    context: ReactContext < T > ,
    renderLanes: Lanes,
): void {
    let fiber = workInProgress.child;
    if (fiber !== null) {
        fiber.return = workInProgress;
    }
    // 從子節點開始匹配是否存在消費了當前 Context 的節點
    while (fiber !== null) {
        let nextFiber;

        const list = fiber.dependencies;
        if (list !== null) {
            nextFiber = fiber.child;

            let dependency = list.firstContext;
            while (dependency !== null) {
                // 1. 判斷 fiber 節點的 context 和當前 context 是否匹配
                if (dependency.context === context) {
                    // 2. 匹配時,給當前節點調度一個更新任務
                    if (fiber.tag === ClassComponent) {}

                    fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
                    const alternate = fiber.alternate;
                    if (alternate !== null) {
                        alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
                    }
                    // 3. 向上標記 childLanes
                    scheduleContextWorkOnParentPath(
                        fiber.return,
                        renderLanes,
                        workInProgress,
                    );

                    list.lanes = mergeLanes(list.lanes, renderLanes);
                    break;
                }
                dependency = dependency.next;
            }
        } else if (fiber.tag === ContextProvider) {} else if (fiber.tag === DehydratedFragment) {} else {}

        // ...
        fiber = nextFiber;
    }
}

核心邏輯:

  1. 從 ContextProvider 的節點出發,向下查找所有 fiber.dependencies 依賴當前 Context 的節點
  2. 找到消費節點時,從當前節點出發,向上回溯標記父節點 fiber.childLanes,標識其子節點需要更新,從而保證了所有消費了該 Context 的子節點都會被重新渲染,實現了 Context 的更新

總結

  1. 在消費階段,消費者通過 readContext 獲取最新狀態,并通過 fiber.dependencies 關聯當前 Context
  2. 在更新階段,從 ContextProvider 節點出發查找所有消費了該 context 的節點

如何避免 Context 引起的 re-render

從上面分析 Context 的整個工作流程,我們可以知道當 ContextProvider 接收到 value 變化時就會找到所有消費了該 Context 的組件進行 re-render,若 ContextProvider 的 value 是一個對象時,即使沒有使用到發生變化的 value 的組件也會造成多次不必要的 re-render。

那我們怎么做優化呢?直接說方案:

  1. 將 ContextProvider 的值做 memoize 處理
  2. 對數據和 API 做拆分(或者說是將 getter(state)和 setter(API)做拆分)
  3. 對數據做拆分(細粒度拆分)
  4. Context Selector

具體的 case 可參考上述提到的優化文章:React re-renders guide: everything, all at once。

接下來開始我們今天的重點:Context Selector。開始之前先來個 case1:

import React, { useState } from "react";
const StateContext = React.createContext(null);

const StateProvider = ({ children }) => {
 console.log("StateProvider render");
 
 const [count1, setCount1] = useState(1);
 const [count2, setCount2] = useState(1);
 return (
  <StateContext.Provider 
   value={{ count1, setCount1, count2, setCount2 }}>
   {children}
  </StateContext.Provider>
 );
};

const Counter1 = () => {
 console.log("count1 render");
 
 const { count1, setCount1 } = React.useContext(StateContext);
 return (
  <>
   <div>Count1: {count1}</div>
   <button 
    onClick={() => setCount1((n) => n + 1)}>setCount1</button>
 </>
);
};

const Counter2 = () => {
 console.log("count2 render");
 
 const { count2, setCount2 } = React.useContext(StateContext);
 
 return (
  <>
   <div>Count2: {count2}</div>
   <button onClick={() => setCount2((n) => n + 1)}>setCount2</button>
  </>
 );
};

const App = () => {
 return (
  <StateProvider>
   <Counter1 />
   <Counter2 />
  </StateProvider>
 );
};

export default App;

?

開發環境記得關閉 StrictMode 模式,否則每次 re-render 都會走兩遍。具體使用方式和 StrictMode 的意義可參考官方文檔。

?

通過上面的 case,我們會發現在 count1 觸發更新時,即使 Counter2 沒有使用 count1 也會進行 re-render。這是因為 count1 的更新會引起 StateProvider 的 re-render,從而會導致 StateProvider 的 value 生成全新的對象,觸發 ContextProvider 的 re-render,找到當前 Context 的所有消費者進行 re-render。

如何做到只有使用到 Context 的 value 改變才觸發組件的 re-render 呢?社區有一個對應的解決方案 dai-shi/use-context-selector: React useContextSelector hook in userland。

接下來我們改造一下上述的 case2:

import React, { useState } from 'react';
import { createContext, useContextSelector } from 'use-context-selector';

const context = createContext(null);

const Counter1 = () => {
  const count1 = useContextSelector(context, v => v[0].count1);
  const setState = useContextSelector(context, v => v[1]);
  const increment = () => setState(s => ({
    ...s,
    count1: s.count1 + 1,
  }));
  return (
    <div>
      <span>Count1: {count1}</span>
      <button type="button" onClick={increment}>+1</button>
      {Math.random()}
    </div>
  );
};

const Counter2 = () => {
  const count2 = useContextSelector(context, v => v[0].count2);
  const setState = useContextSelector(context, v => v[1]);
  const increment = () => setState(s => ({
    ...s,
    count2: s.count2 + 1,
  }));
  return (
    <div>
      <span>Count2: {count2}</span>
      <button type="button" onClick={increment}>+1</button>
      {Math.random()}
    </div>
  );
};

const StateProvider = ({ children }) => (
  <context.Provider value={useState({ count1: 0, count2: 0 })}>
    {children}
  </context.Provider>
);

const App = () => (
  <StateProvider>
    <Counter1 />
    <Counter2 />
  </StateProvider>
);

export default App

這時候問題來了,不是說好精準渲染的嗎?怎么還是都會進行 re-render。解決方案:將 react 改為 v17 版本(v17對應的case3),后面我們再說具體原因(只想說好坑..)。

use-context-selector

接下來我們主要分析下 createContext 和 useContextSelector 都做了什么(官方還有其他的 API ,感興趣的朋友可以自行查看,核心還是這兩個 API)。

createContext

簡化一下,只看核心邏輯:

import { createElement, useLayoutEffect, useRef, createContext as createContextOrig } from 'react'
const CONTEXT_VALUE = Symbol();
const ORIGINAL_PROVIDER = Symbol();

const createProvider = (
  ProviderOrig
) => {
  const ContextProvider = ({ value, children }) => {
    const valueRef = useRef(value);
    const contextValue = useRef();
    
    if (!contextValue.current) {
      const listeners = new Set();
      contextValue.current = {
        [CONTEXT_VALUE]: {
          /* "v"alue     */ v: valueRef,
          /* "l"isteners */ l: listeners,
        },
      };
    }
    useLayoutEffect(() => {
      valueRef.current = value;
  contextValue.current[CONTEXT_VALUE].l.forEach((listener) => {
          listener({ v: value });
        });
    }, [value]);
    
    return createElement(ProviderOrig, { value: contextValue.current }, children);
  };
  return ContextProvider;
};

export function createContext(defaultValue) {
  const context = createContextOrig({
    [CONTEXT_VALUE]: {
      /* "v"alue     */ v: { current: defaultValue },
      /* "l"isteners */ l: new Set(),
    },
  });
  context[ORIGINAL_PROVIDER] = context.Provider;
  context.Provider = createProvider(context.Provider);
  delete context.Consumer; // no support for Consumer
  return context;
}

對原始的 createContext 包一層,同時為了避免 value 的意外更新造成消費者的不必要 re-render ,將傳遞給原始的 createContext 的 value 通過 uesRef 進行存儲,這樣在 React 內部對比新舊 value 值時就不會再操作 re-render(后續 value 改變后派發更新時就需要通過 listener 進行 re-render 了),最后返回包裹后的 createContext 給用戶使用。

useContextSelector

接下來看下簡化后的 useContextSelector :

export function useContextSelector(context, selector) {
 const contextValue = useContextOrig(context)[CONTEXT_VALUE];
 const {
 /* "v"alue */ v: { current: value },
 /* "l"isteners */ l: listeners
 } = contextValue;
 
 const selected = selector(value);
 const [state, dispatch] = useReducer(
  (prev, action) => {
   if ("v" in action) {
    if (Object.is(prev[0], action.v)) {
     return prev; // do not update
    }
    const nextSelected = selector(action.v);
    if (Object.is(prev[1], nextSelected)) {
     return prev; // do not update
    }
    return [action.v, nextSelected];
   }
  },
  [value, selected]
 );
 
 useLayoutEffect(() => {
  listeners.add(dispatch);
  return () => {
   listeners.delete(dispatch);
  };
 
 }, [listeners]);
 
 return state[1];
}

核心邏輯:

  1. 每次渲染時,通過 selector 和 value 獲取最新的 selected
  2. 同時將 useReducer 對應的 dispatch 添加到 listeners
  3. 當 value 改變時,就會執行 listeners 中收集到 dispatch 函數,從而在觸發 reducer 內部邏輯,通過對比 value 和 selected 是否有變化,來決定是否觸發當前組件的 re-render

在 react v18 下的 bug

回到上面的 case 在 react v18 的表現和在原始 Context 的表現幾乎一樣,每次都會觸發所有消費者的 re-render。再看 use-context-selector 內部是通過 useReducer 返回的 dispatch 函數派發組件更新的。

接下來再看下 useReducer 在 react v18 和 v17 版本到底有什么不一樣呢?看個簡單的 case:

import React, { useReducer } from "react";

const initialState = 0;
const reducer = (state, action) => {
 switch (action) {
  case "increment":
   return state;
  default:
   return state;
 }

};

export const App = () => {
 console.log("UseReducer Render");
 const [count, dispatch] = useReducer(reducer, initialState);
 
 return (
  <div>
   <div>Count = {count}</div>
   <button onClick={() => dispatch("increment")}>Inacrement</button>
  </div>
 );
};

簡單描述下:多次點擊按鈕「Inacrement」,在 react 的 v17 和 v18 版本分別會有什么表現?

先說結論:

  • v17:只有首次渲染會觸發 App 組件的 render,后續點擊將不再觸發 re-render
  • v18:每次都會觸發 App 組件的 re-render(即使狀態沒有實質性的變化也會觸發 re-render)

這就要說到【eager state 策略】了,在 React 內部針對多次觸發更新,而最后狀態并不會發生實質性變化的情況,組件是沒有必要渲染的,提前就可以中斷更新了。

也就是說 useReducer 內部是有做一定的性能優化的,而這優化會存在一些 bug,最后 React 團隊也在 v18 后移除了該優化策略(注:useState 還是保留該優化),詳細可看該相關 PR Remove usereducer eager bailout。當然該 PR 在社區也存在一些討論(Bug: useReducer and same state in React 18),畢竟無實質性的狀態變更也會觸發 re-render,對性能還是有一定影響的。

回歸到 useContextSelector ,無優化版本的 useReducer 又是如何每次都觸發組件 re-render 呢?

具體原因:在上面 useReducer 中,是通過 Object.is 判斷 value 是否發生了實質性變化,若沒有,就返回舊的狀態,在 v17 有優化策略下,就不會再去調度更新任務了,而在 v18 沒有優化策略的情況下,每次都會調度新的更新任務,從而引發組件的 re-render。

通過 useSyncExternalStore 優化

通過分析知道造成 re-render 的原因是使用了 useReducer,那就不再依賴該 hook,使用 react v18 新的 hook useSyncExternalStore 來實現 useContextSelector(優化后的 case4)。

export function useContextSelector(context, selector) {
 const contextValue = useContextOrig(context)[CONTEXT_VALUE];
 const {
 /* "v"alue */ v: { current: value },
 /* "l"isteners */ l: listeners
 } = contextValue;
 
 const lastSnapshot = useRef(selector(value));
 const subscribe = useCallback(
  (callback) => {
   listeners.add(callback);
   return () => {
    listeners.delete(callback);
   };
  },
  [listeners]
 );
 
 const getSnapshot = () => {
  const {
  /* "v"alue */ v: { current: value }
  } = contextValue;
  
  const nextSnapshot = selector(value);
  lastSnapshot.current = nextSnapshot;
  return nextSnapshot;
 };
 
 return useSyncExternalStore(subscribe, getSnapshot);
}

實現思路:

  1. 收集訂閱函數 subscribe 的 callback(即 useSyncExternalStore 內部的 handleStoreChange )
  2. 當 value 發生變化時,觸發 listeners 收集到的 callback ,也就是執行 handleStoreChange 函數,通過 getSnapshot 獲取新舊值,并通過 Object.is 進行對比,判斷當前組件是否需要更新,從而實現了 useContextSelector 的精確更新

當然除了 useReducer 對應的性能問題,use-context-selector 還存在其他的性能,感興趣的朋友可以查看這篇文章從 0 實現 use-context-selector。同時,use-context-selector 也是存在一些限制,比如說不支持 Class 組件、不支持 Consumer …

針對上述文章中,作者提到的問題二和問題三,個人認為這并不是 use-context-selector 的問題,而是 React 底層自身帶來的問題。比如說:問題二,React 組件是否 re-render 跟是否使用了狀態是沒有關系的,而是和是否觸發了更新狀態的 dispatch 有關,如果一定要和狀態綁定一起,那不就是 Vue 了嗎。對于問題三,同樣是 React 底層的優化策略處理并沒有做到極致這樣。

總結

回到 React Context 工作原理來看,只要有消費者訂閱了該 Context,在該 Context 發生變化時就會觸達所有的消費者。也就是說整個工作流程都是以 Context 為中心的,那只要把 Context 拆分的粒度足夠小就不會帶來額外的渲染負擔。但是這樣又會帶來其他問題:ContextProvider 會嵌套多層,同時對于粒度的把握對開發者來說又會帶來一定的心智負擔。

從另一條路出發:Selector 機制,通過選擇需要的狀態從而規避掉無關的狀態改變時帶來的渲染開銷。除了社區提到的 use-context-selector ,React 團隊也有一個相應的 RFC 方案 RFC: Context selectors,不過這個 RFC 從 19 年開始目前還處于持續更新階段。

最后,對于 React Context 的使用,個人推薦:「不頻繁更改的全局狀態(比如說:自定義主題、賬戶信息、權限信息等)可以合理使用 Context,而對于其他頻繁修改的全局狀態可以通過其他數據流方式維護,可以更好的避免不必要的 re-render 開銷」。

參考

  1. https://www.developerway.com/posts/react-re-renders-guide
  2. https://react.dev/reference/react/StrictMode#enabling-strict-mode-for-entire-app
  3. https://github.com/dai-shi/use-context-selector
  4. https://github.com/facebook/react/pull/22445
  5. https://github.com/facebook/react/issues/24596
  6. https://react.dev/reference/react/useSyncExternalStore
  7. https://juejin.cn/post/7197972831795380279
  8. https://github.com/reactjs/rfcs/pull/119
  9. case1:https://codesandbox.io/s/serverless-frost-9ryw2x?file=/src/App.js
  10. case2:https://codesandbox.io/s/use-context-selector-vvs93q?file=/src/App.js
  11. case3:https://codesandbox.io/s/elegant-montalcini-nkrvlh?file=/src/App.js
  12. case4:https://codesandbox.io/s/use-context-selector-smsft3?file=/src/App.js
責任編輯:武曉燕 來源: 大轉轉FE
相關推薦

2022-10-27 11:23:26

GoFrame共享變量

2021-08-10 07:41:24

ContextWaitGroupGoroutine

2022-05-13 08:48:50

React組件TypeScrip

2023-12-21 10:26:30

??Prettier

2017-07-26 11:32:50

NETRabbitMQ系統集成

2015-11-26 10:53:45

LinuxWindowsMac OS

2021-09-26 09:40:25

React代碼前端

2022-07-31 19:57:26

react項目VSCode

2021-08-26 14:55:55

開發React代碼

2022-09-14 08:16:48

裝飾器模式對象

2021-03-28 09:17:18

JVM場景鉤子函數

2021-12-13 14:37:37

React組件前端

2021-12-07 08:16:34

React 前端 組件

2025-07-09 07:20:00

GORMGo分頁

2022-06-02 10:02:47

Kubectl更新應用Linux

2023-06-28 08:25:14

事務SQL語句

2021-03-12 18:25:09

開發前端React

2023-07-03 07:51:47

2019-11-15 09:58:04

LinuxAsciinemapython

2020-12-20 10:02:17

ContextReactrender
點贊
收藏

51CTO技術棧公眾號

午夜亚洲伦理| 9999热视频在线观看| 免费人成在线不卡| 麻豆一区二区在线观看| 久久久高清视频| 久久毛片亚洲| 亚洲欧美一区二区三区极速播放| 国产传媒一区二区| 亚洲欧美一二三区| 欧美在线首页| 亚洲午夜久久久久久久| 国产九九九视频| 伊人成综合网站| 亚洲日本一区二区| 欧美国产综合视频| 国产毛片毛片毛片毛片毛片| 性欧美长视频| 久久夜色精品国产| 成年人免费观看视频网站| 国产精品久久久久久久久久辛辛| 第一福利永久视频精品| 2025韩国大尺度电影| 天天射,天天干| 韩国v欧美v日本v亚洲v| 国产91精品网站| 久久99久久久| 91九色精品| 国产亚洲精品久久久久久777| 国产人妻精品午夜福利免费| 91成人在线| 欧美日韩国产专区| 国产 欧美 日韩 一区| 免费**毛片在线| 国产午夜精品一区二区三区视频 | 男女啪啪的视频| 免费黄色在线视频网站| 成人免费福利片| 91久久精品视频| 欧美在线视频精品| 日韩精品一级二级| 啪一啪鲁一鲁2019在线视频| 国产主播在线观看| 欧美日韩免费| 久久91亚洲精品中文字幕| 污污视频网站在线免费观看| 欧美色图在线播放| 亚洲网址你懂得| 91视频在线网站| 精品中文字幕一区二区三区av| 日韩精品极品毛片系列视频| 99re久久精品国产| 少妇久久久久| 亚洲女人被黑人巨大进入al| 在线免费观看a级片| 卡通动漫国产精品| 亚洲国产古装精品网站| 女同性恋一区二区三区| 精品久久对白| 精品亚洲精品福利线在观看| 玖草视频在线观看| 蜜臀av免费一区二区三区| 精品中文字幕久久久久久| 亚洲av网址在线| 亚洲精品一级二级三级| 亚洲色图激情小说| 成年人在线免费看片| 精品一二三区| 日韩性生活视频| 国产三级国产精品国产国在线观看| 亚洲欧美偷拍自拍| 欧美黑人狂野猛交老妇| 日本一区二区网站| 小嫩嫩精品导航| 国产成人亚洲综合91| 久久精品导航| 欧美精品一卡| 亚洲国产欧美日韩精品| 中文字幕 日本| 亚洲深夜福利在线观看| 在线精品91av| 黄色一级大片在线免费观看| 欧美国产精品| **欧美日韩vr在线| 日批视频免费观看| 国产一区二区视频在线播放| 高清国产在线一区| 免费毛片在线| 亚洲欧美激情小说另类| 91免费黄视频| 91看片一区| 欧美一区二区三区成人| 老司机午夜免费福利| 九色精品国产蝌蚪| 久久综合伊人77777尤物| 精品一区免费观看| 日韩和欧美的一区| 国产欧美日韩最新| 国精产品一品二品国精品69xx| 91蝌蚪porny九色| 亚洲成年人专区| 女人让男人操自己视频在线观看| 欧美三级电影一区| 四季av综合网站| 99精品国产一区二区三区| 午夜精品一区二区三区在线| 国产99久久久久久免费看| 成人免费视频播放| 天堂精品视频| 麻豆免费在线| 日韩欧美在线综合网| 日韩av在线看免费观看| 好看不卡的中文字幕| 国产精品久久久久99| 殴美一级特黄aaaaaa| 国产精品久久国产精麻豆99网站| 青青草国产免费| 日韩美女在线| 亚洲国产精品国自产拍av秋霞| 色欲一区二区三区精品a片| 亚洲男人影院| 国产精品二区二区三区| 乱人伦中文视频在线| 一本大道久久a久久精品综合| 免费av不卡在线| 欧美精选一区二区三区| 18性欧美xxxⅹ性满足| 亚洲av综合色区无码一区爱av | 国产精品一区二区三区精品 | 国产女同互慰高潮91漫画| 999一区二区三区| www久久久| 色久欧美在线视频观看| 无码人妻精品一区二区三区蜜桃91 | 日韩在线视频精品| 日本成人在线视频网址| 欧美一级做性受免费大片免费| 自拍偷拍欧美精品| 在线观看高清免费视频| 神马久久一区二区三区| 57pao成人永久免费视频| 精品人妻一区二区三区换脸明星| 亚洲欧洲av另类| 99热一区二区| 日韩在线第七页| 国产日韩在线看| 1pondo在线播放免费| 欧美三级资源在线| 手机看片日韩av| 秋霞成人午夜伦在线观看| 日韩av电影免费在线| 午夜精品成人av| 亚洲欧美日韩一区在线| 日本高清不卡码| 久久久久久麻豆| 日本爱爱免费视频| 成人中文在线| 成人黄在线观看| 国产黄a三级三级三级av在线看 | 国产mv免费观看入口亚洲| 国产精品婷婷午夜在线观看| 97精品国产97久久久久久春色| av免费在线不卡| 亚洲精品乱码久久久久久黑人| 在线观看av免费观看| 欧美日韩在线大尺度| 91系列在线观看| www.亚洲.com| 色噜噜狠狠一区二区三区果冻| 毛茸茸多毛bbb毛多视频| 黄色精品视频| 一区二区三区在线观看视频| 国产高清999| 91成人观看| 91手机在线观看| www中文字幕在线观看| 亚洲激情第一页| 麻豆成人免费视频| 成人不卡免费av| 国产精品视频大全| 成人资源www网在线最新版| 欧美伊人精品成人久久综合97| 成人激情五月天| 精品一区二区日韩| 免费的一级黄色片| 全国精品免费看| 国产精品久久久久久久久久三级| 毛片基地黄久久久久久天堂| 成人亚洲欧美一区二区三区| 怡红院av在线| 亚洲国产日韩一区| 波多野结衣大片| 亚洲免费观看高清完整版在线观看| 特级特黄刘亦菲aaa级| 天堂一区二区在线| 日本老太婆做爰视频| 日韩美女国产精品| 国产一区视频在线| 免费高潮视频95在线观看网站| 日韩在线观看网址| 午夜av免费在线观看| 欧美日韩高清在线| 欧美另类一区二区| 亚洲久草在线视频| 国产特级黄色录像| 国产精品1区2区3区| 黄色三级视频片| 在线 亚洲欧美在线综合一区| 日韩欧美视频一区二区| 久久男人av| 亚洲一区二区三区777| 亚洲高清黄色| 韩国19禁主播vip福利视频| 网友自拍视频在线| 亚洲欧美国产一区二区三区| 性猛交富婆╳xxx乱大交天津| 91成人免费网站| 1级黄色大片儿| 亚洲欧美成人一区二区三区| 天天舔天天操天天干| 9久草视频在线视频精品| 中文字幕第三区| 日韩av成人高清| 国内外成人激情视频| 国产精品第十页| japanese在线视频| 日本不卡高清| 日韩欧美精品一区二区| 亚洲影院天堂中文av色| 国产一区国产精品| 99a精品视频在线观看| 亚洲一区二区久久久久久久| 青青青国产精品| 国产精品久久久久久久久久ktv| 毛片免费看不卡网站| 18久久久久久| 亚洲伊人av| 91成人天堂久久成人| 精精国产xxxx视频在线野外| 久久久久久久国产| 欧洲中文在线| 欧美高清视频在线播放| 中文av资源在线| 色综合久久久久久中文网| av免费在线观| 九九热最新视频//这里只有精品| 高清全集视频免费在线| 久久综合国产精品台湾中文娱乐网| 日本视频在线观看| 日韩一区二区三区xxxx| 巨大荫蒂视频欧美另类大| 久久精品电影一区二区| 黄色动漫在线| 欧美日韩福利视频| 欧美高清另类hdvideosexjaⅴ| 欧美激情精品久久久久久变态| 日本不卡影院| 午夜精品久久久久久99热软件| 国产夫妻在线| 欧美专区日韩视频| 日韩一级二级| 91日本视频在线| 亚洲视频国产| 精品欧美一区二区在线观看视频| 欧美三级午夜理伦三级小说| 蜜桃视频在线观看成人| 精品国产一级毛片| 亚洲欧洲精品一区二区| 亚洲人成免费网站| 日韩精品综合在线| 一区二区精品| www.99在线| 国产激情视频一区二区在线观看| 丰满少妇xbxb毛片日本| 久久色视频免费观看| 日本综合在线观看| 亚洲女人的天堂| 国产成人愉拍精品久久 | 久久国产在线观看| 欧美日韩精品在线| 中文字幕+乱码+中文乱码91| 欧美一区二区观看视频| 天天av天天翘| 中文字幕日韩欧美| av午夜在线观看| 国产精品久久久久77777| 亚洲啊v在线免费视频| 久久一区二区精品| 91精品91| www.国产区| 国产精品原创巨作av| 欧美 变态 另类 人妖| 国产精品毛片久久久久久| 久久这里只有精品免费| 在线观看视频一区二区| 国内老熟妇对白hdxxxx| 亚洲视频在线观看网站| 手机在线免费看av| 国产精品wwww| 国产成人精品福利| 咪咪色在线视频| 久久久免费看| 国产亚洲人成a在线v网站| 鬼打鬼之黄金道士1992林正英| 国产精品亚洲片在线播放| 手机看片日韩国产| 久久国产精品久久久久久电车| 色91精品久久久久久久久| 99精品视频在线观看| 日本不卡一二区| 色吊一区二区三区| 内射后入在线观看一区| 日韩中文字幕网址| 超碰一区二区| 国产精品xxxx| 你懂的视频一区二区| 一区二区三区免费播放| thepron国产精品| 免费看一级大片| 欧美三级在线视频| 欧美xxx.com| 午夜精品一区二区三区在线| www.欧美| 一区二区不卡在线观看| 久久久久国产精品一区二区| 午夜福利三级理论电影| 亚洲免费看黄网站| 亚洲中文字幕在线观看| 一本色道久久88亚洲综合88| 欧美gv在线| 精品国产乱码久久久久| 欧美片第1页综合| 亚洲第一成肉网| 国产精品久久久久影院老司 | 亚洲三级观看| 亚洲女则毛耸耸bbw| 亚洲美女一区二区三区| 国产精品久久久久久久久毛片 | 向日葵视频成人app网址| 国产一区二区三区色淫影院| 伊人久久大香线蕉av超碰演员| japan高清日本乱xxxxx| 亚洲蜜桃精久久久久久久| 国产视频手机在线| 久久影视电视剧免费网站| 欧美一级做a| av电影一区二区三区| 紧缚奴在线一区二区三区| 久久国产精品国语对白| 制服丝袜亚洲网站| 亚洲丝袜精品| 国产福利久久精品| 99在线精品视频在线观看| 午夜一区二区三区免费| 日韩欧美在线视频观看| 撸视在线观看免费视频| 国产精品久久久久久av福利| 一区二区自拍| 青青久久av北条麻妃黑人| 日本中文字幕在线视频观看| 日韩精品久久久久久| 久久久久久久久久久国产精品| 日韩欧美精品免费在线| 你懂的视频在线播放| 国产不卡精品视男人的天堂| 国产一区二区三区四区二区| 热久久精品免费视频| 激情亚洲另类图片区小说区| 免费的一级黄色片| a级精品国产片在线观看| 天堂网一区二区三区| 亚洲女人天堂网| 成人一级视频| 天天综合五月天| www.欧美色图| 日韩欧美国产另类| 精品国模在线视频| 99re8这里有精品热视频8在线| 久色视频在线播放| 国产婷婷色一区二区三区| 在线观看色网站| 欧美激情一区二区久久久| 亚洲精品国产精品粉嫩| 自拍偷拍一区二区三区四区| 一区二区高清免费观看影视大全| 午夜av免费在线观看| 国产精品一区二区久久久久| 国产精品地址| 国产又粗又黄又猛| 日韩欧美123| 黄色综合网址| 亚洲国产精品女人| 久久综合久久99| 99国产揄拍国产精品| 欧美一区二区三区免费观看| 我不卡影院28| 亚洲一级中文字幕| 欧美一级精品在线| 欧美电影网站| 日本阿v视频在线观看| 欧美国产欧美综合|