1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        不優(yōu)雅的 React Hooks

        共 10306字,需瀏覽 21分鐘

         ·

        2022-01-12 14:22

        本文由 螞蟻 RichLab 前端團(tuán)隊(duì) 墨書授權(quán)轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明原作者

        時(shí)至 2021 年年底,React Hooks 已在 React 生態(tài)中大放異彩,席卷了幾乎所有的 React 應(yīng)用。而其又與 Function Component 以及 Fiber 架構(gòu)幾近天作之合,在當(dāng)下,我們好像毫無拒絕它的道理。

        誠(chéng)然,Hooks 解決了 React Mixins 這個(gè)老大難的問題,但從它各種奇怪的使用體驗(yàn)上來說,我認(rèn)為現(xiàn)階段的 Hooks 并不是一個(gè)好的抽象。

        紅臉太常見,也來唱個(gè)黑臉,本文將站在一個(gè)「挑刺兒」的視角,聊聊我眼中的 React Hooks ~

        「奇怪的」規(guī)矩

        React 官方制定了一些 Hooks 書寫規(guī)范用來規(guī)避 Bug,但這也恰恰暴露了它存在的問題。

        命名

        Hooks 并非普通函數(shù),我們一般用use開頭命名,以便與其他函數(shù)區(qū)分。

        但相應(yīng)地,這也破壞了函數(shù)命名的語義。固定的use前綴使 Hooks 很難命名,你既為useGetState這樣的命名感到困惑,也無法理解useTitle到底是怎么個(gè)use法兒。

        相比較而言,以_開頭的私有成員變量和$尾綴的流,則沒有類似的困擾。 當(dāng)然,這只是使用習(xí)慣上的差異,并不是什么大問題。

        調(diào)用時(shí)序

        在使用useState的時(shí)候,你有沒有過這樣的疑惑:useState雖然每次render() 都會(huì)調(diào)用,但卻可以為我保持住 State,如果我寫了很多個(gè),那它怎么知道我想要的是什么 State 呢?

        const?[name,?setName]?=?useState('xiaoming')
        console.log('some?sentences')
        const?[age,?setAge]?=?useState(18)

        兩次useState只有參數(shù)上的區(qū)別,而且也沒有語義上的區(qū)分(我們僅僅是給返回值賦予了語義),站在 useState的視角,React 怎么知道我什么時(shí)候想要name而什么時(shí)候又想要age的呢?

        以上面的示例代碼來看,為什么第 1 行的useState會(huì)返回字符串name,而第 3 行會(huì)返回?cái)?shù)字age呢? 畢竟看起來,我們只是「平平無奇」地調(diào)用了兩次useState而已。

        答案是「時(shí)序」。useState的調(diào)用時(shí)序決定了結(jié)果,也就是,第一次的useState「保存」了 name 的狀態(tài),而第二次「保存」了age的狀態(tài)。

        //?Class?Component?中通過字面量聲明與更新?State,無一致性問題
        this.setState({
        ??name:?'xiaoming',??//?State?字面量?`name`,`age`
        ??age:?18,
        })

        React 簡(jiǎn)單粗暴地用「時(shí)序」決定了這一切(背后的數(shù)據(jù)結(jié)構(gòu)是鏈表),這也導(dǎo)致 Hooks 對(duì)調(diào)用時(shí)序的嚴(yán)格要求。也就是要避免所有的分支結(jié)構(gòu),不能讓 Hooks 「時(shí)有時(shí)無」。

        //???典型錯(cuò)誤
        if?(some)?{
        ??const?[name,?setName]?=?useState('xiaoming')
        }

        這種要求完全依賴開發(fā)者的經(jīng)驗(yàn)抑或是 Lint,而站在一般第三方 Lib 的角度看,這種要求調(diào)用時(shí)序的 API 設(shè)計(jì)是極為罕見的,非常反直覺。

        最理想的 API 封裝應(yīng)當(dāng)是給開發(fā)者認(rèn)知負(fù)擔(dān)最小的。好比封裝一個(gè)純函數(shù)add(),不論開發(fā)者是在什么環(huán)境調(diào)用、在多么深的層級(jí)調(diào)用、用什么樣的調(diào)用時(shí)序,只要傳入的參數(shù)符合要求,它就可以正常運(yùn)作,簡(jiǎn)單而純粹。

        function?add(a:?number,?b:?number)?{
        ??return?a?+?b
        }

        function?outer()?{
        ??const?m?=?123;
        ??setTimeout(()?=>?{
        ????request('xx').then((n)?=>?{
        ??????const?result?=?add(m,?n)?????????//?符合直覺的調(diào)用:無環(huán)境要求
        ????})
        ??},?1e3)
        }

        可以說「React 確實(shí)沒辦法讓 Hooks 不要求環(huán)境」,但也不能否認(rèn)這種方式的怪異。

        類似的情況在redux-saga里也有,開發(fā)者很容易寫出下面這種「符合直覺」的代碼,而且怎么也「看」不出有問題。

        import?{?call?}?from?'redux-saga/effects'

        function*?fetch()?{
        ??setTimeout(function*?()?{
        ????const?user?=?yield?call(fetchUser)
        ????console.log('hi',?user)??????????????????//?不會(huì)執(zhí)行到這兒
        ??},?1e3)
        }

        yield call()在 Generator 里調(diào)用,看起來真的很「合理」。但實(shí)際上,function*需要 Generator 執(zhí)行環(huán)境,而call也需要redux-saga的執(zhí)行環(huán)境。雙重要求之下,實(shí)例代碼自然無法正常運(yùn)行。

        useRef 的「排除萬難」

        從本義上來說,useRef其實(shí)是 Class Component 時(shí)代React.createRef()的等價(jià)替代。

        官方文檔[1]中最開始的示例代碼可以佐證這一點(diǎn)(如下所示,有刪減):

        function?TextInputWithFocusButton()?{
        ??const?inputEl?=?useRef(null);
        ??return?(
        ????type="text"?/>
        ??);
        }

        但因?yàn)槠鋵?shí)現(xiàn)特殊,也常作他用。

        React Hooks 源碼中,useRef僅在 Mount 時(shí)期初始化對(duì)象,而 Update 時(shí)期返回 Mount 時(shí)期的結(jié)果(memoizedState)。這意味著一次完整的生命周期中,useRef 保留的引用始終不會(huì)改變。

        而這一特點(diǎn)卻讓它成為了 Hooks 閉包救星。

        「遇事不決,useRef !」(useRef存在許多濫用的情況,本文不多贅述)

        每一個(gè) Function 的執(zhí)行都有與之相應(yīng)的 Scope,對(duì)于面向?qū)ο髞碚f,this引用即是連接了所有 Scope 的 Context(當(dāng)然前提是在同一個(gè) Class 下)。

        class?Runner?{
        ??runCount?=?0

        ??run()?{
        ????console.log('run')
        ????this.runCount?+=?1
        ??}

        ??xrun()?{
        ????this.run()
        ????this.run()
        ????this.run()
        ??}

        ??output()?{
        ????this.xrun()
        ????//?即便是「間接調(diào)用」`run`,這里「仍然」能獲取?`run`?的執(zhí)行信息
        ????console.log(this.runCount)?//?3
        ??}
        }

        在 React Hooks 中,每一次的 Render 由彼時(shí)的 State 決定,Render 完成 Context 即刷新。優(yōu)雅的 UI 渲染,干凈而利落。

        useRef多少違背了設(shè)計(jì)者的初衷, useRef可以橫跨多次 Render 生成的 Scope,它能保留下已執(zhí)行的渲染邏輯,卻也能使已渲染的 Context 得不到釋放,威力無窮卻也作惡多端

        而如果說 this引用是面向?qū)ο笾凶钪饕母弊饔?,那?useRef亦同。從這一點(diǎn)來說,擁有 useRef寫法的 Function Component 注定難以達(dá)成「函數(shù)式」。

        小心使用

        有缺陷的生命周期

        構(gòu)造時(shí)

        Class Component 和 Function Component 之間還有一個(gè)很大的「Bug」,Class Component 僅實(shí)例化一次后續(xù)僅執(zhí)行 render() ,而 Function Component 卻是在不斷執(zhí)行自身。

        這導(dǎo)致 Function Component 相較 Class Component 實(shí)際缺失了對(duì)應(yīng)的constructor構(gòu)造時(shí)。當(dāng)然如果你有辦法只讓 Function 里的某段邏輯只執(zhí)行一遍,倒是也可以模擬出constructor

        //?比如使用?useRef?來構(gòu)造
        function?useConstructor(callback)?{
        ??const?init?=?useRef(true)
        ??if?(init.current)?{
        ????callback()
        ????init.current?=?false
        ??}
        }

        生命周期而言, constructor 不能類同 useEffect ,如果實(shí)際節(jié)點(diǎn)渲染時(shí)長(zhǎng)較長(zhǎng),二者會(huì)有很大時(shí)差。

        也就是說,Class Component 和 Function Component 的生命周期 API 并不能完全一一對(duì)應(yīng),這是一個(gè)很引發(fā)錯(cuò)誤的地方。

        設(shè)計(jì)混亂的 useEffect

        在了解useEffect的基本用法后,加上對(duì)其字面意思的理解(監(jiān)聽副作用),你會(huì)誤以為它等同于 Watcher。

        useEffect(()?=>?{
        ??//?watch?到?`a`?的變化
        ??doSomething4A()
        },?[a])

        但很快你就會(huì)發(fā)現(xiàn)不對(duì)勁,如果變量a未能觸發(fā) re-render,監(jiān)聽并不會(huì)生效。也就是說,實(shí)際還是應(yīng)該用于監(jiān)聽 State 的變化,即useStateEffect。但參數(shù)deps卻并未限制僅輸入 State。如果不是為了某些特殊動(dòng)作,很難不讓人認(rèn)為是設(shè)計(jì)缺陷。

        const?[a]?=?useState(0)
        const?[b]?=?useState(0)

        useEffect(()?=>?{
        ????//?假定此處為?`a`?的監(jiān)聽
        },?[a])

        useEffect(()?=>?{
        ????//?假定此處為?`b`?的監(jiān)聽
        ??//?實(shí)際即便?`b`?未變化也并未監(jiān)聽?`a`,但此處仍然因?yàn)闀?huì)因?yàn)?`a`?變化而執(zhí)行
        },?[b,?Date.now()])????????//?因?yàn)?Date.now()?每次都是新的值

        useStateEffect的理解也并不到位,因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">useEffect實(shí)際還負(fù)責(zé)了 Mount 的監(jiān)聽,你需要用「空依賴」來區(qū)分 Mount 和 Update。

        useEffect(onMount,?[])

        單一 API 支持的能力越多,也意味著其設(shè)計(jì)越混亂。復(fù)雜的功能不僅考驗(yàn)開發(fā)者的記憶,也難于理解,更容易因錯(cuò)誤理解而引發(fā)故障。

        useCallback

        性能問題?

        在 Class Component 中我們常常把函數(shù)綁在this上,保持其的唯一引用,以減少子組件不必要的重渲染。

        class?App?{
        ??constructor()?{
        ????//?方法一
        ????this.onClick?=?this.onClick.bind(this)
        ??}
        ??onClick()?{
        ????console.log('I?am?`onClick`')
        ??}

        ??//?方法二
        ??onChange?=?()?=>?{}

        ??render()?{
        ????return?(
        ??????
        ????)
        ??}
        }

        在 Function Component 中對(duì)應(yīng)的方案即 useCallback

        //???有效優(yōu)化
        function?App()?{
        ??const?onClick?=?useCallback(()?=>?{
        ????console.log('I?am?`onClick`')
        ??},?[])

        ??return?()
        }

        //???錯(cuò)誤示范,`onClick`?在每次?Render?中都是全新的,?會(huì)因此重渲染
        function?App()?{
        ??//?...?some?states
        ??const?onClick?=?()?=>?{
        ????console.log('I?am?`onClick`')
        ??}

        ??return?()
        }

        useCallback可以在多次重渲染中仍然保持函數(shù)的引用, 第2行的onClick也始終是同一個(gè),從而避免了子組件的重渲染。

        useCallback源碼其實(shí)也很簡(jiǎn)單:

        Mount 時(shí)期僅保存 callback 及其依賴數(shù)組

        Update 時(shí)期判斷如果依賴數(shù)組一致,則返回上次的 callback

        順便再看看useMemo 的實(shí)現(xiàn),其實(shí)它與 useCallback 的區(qū)別僅僅是多一步 Invoke:

        無限套娃?[2]

        相比較未使用useCallback帶來的性能問題,真正麻煩的是useCallback帶來的引用依賴問題。

        //?當(dāng)你決定引入?`useCallback`?來解決重復(fù)渲染問題
        function?App()?{
        ??//?請(qǐng)求?A?所需要的參數(shù)
        ??const?[a1,?setA1]?=?useState('')
        ??const?[a2,?setA2]?=?useState('')
        ??//?請(qǐng)求?B?所需要的參數(shù)
        ??const?[b1,?setB1]?=?useState('')
        ??const?[b2,?setB2]?=?useState('')

        ??//?請(qǐng)求?A,并處理返回結(jié)果
        ??const?reqA?=?useCallback(()?=>?{
        ????requestA(a1,?a2)
        ??},?[a1,?a2])

        ??//?請(qǐng)求?A、B,并處理返回結(jié)果
        ??const?reqB?=?useCallback(()?=>?{
        ????reqA()???????????????????????????????????????????//?`reqA`的引用始終是最開始的那個(gè),
        ????requestB(b1,?b2)???????????????????????//?當(dāng)`a1`,`a2`變化后`reqB`中的`reqA`其實(shí)是過時(shí)的。
        ??},?[b1,?b2])???????????????????//?當(dāng)然,把`reqA`加到`reqB`的依賴數(shù)組里不就好了?
        ?????????????????????????????????????????????????????????????//?但你在調(diào)用`reqA`這個(gè)函數(shù)的時(shí)候,
        ?????????????????????????????????????????????????????????????????//?你怎么知道「應(yīng)該」要加到依賴數(shù)組里呢?
        ??return?(
        ????<>
        ??????
        ??????
        ????
        ??)
        }

        從上面示例可以看到,當(dāng)useCallback之前存在依賴關(guān)系時(shí),它們的引用維護(hù)也變得復(fù)雜。調(diào)用某個(gè)函數(shù)時(shí)要小心翼翼,你需要考慮它有沒有引用過時(shí)的問題,如有遺漏又沒有將其加入依賴數(shù)組,就會(huì)產(chǎn)生 Bug。

        Use-Universal

        Hooks 百花齊放的時(shí)期誕生了許多工具庫,僅ahooks 就有 62 個(gè)自定義 Hooks,真可謂「萬物皆可 use」~ 真的有必要封裝這么多 Hooks 嗎?又或者說我們真的需要這么多 Hooks 嗎?

        合理封裝?

        盡管在 React 文檔中,官方也建議封裝自定義 Hooks 提高邏輯的復(fù)用性。但我覺得這也要看情況,并不是所有的生命周期都有必要封裝成 Hooks。

        //?1.?封裝前
        function?App()?{
        ??useEffect(()?=>?{???????????//?`useEffect`?參數(shù)不能是?async?function
        ????(async?()?=>?{
        ??????await?Promise.all([fetchA(),?fetchB()])
        ??????await?postC()
        ????})()
        ??},?[])
        ??return?(
        123
        )
        }
        //?--------------------------------------------------

        //?2.?自定義?Hooks
        function?App()?{
        ??useABC()
        ??return?(
        123
        )
        }

        function?useABC()?{
        ??useEffect(()?=>?{
        ????(async?()?=>?{
        ??????await?Promise.all([fetchA(),?fetchB()])
        ??????await?postC()
        ????})()
        ??},?[])
        }
        //?--------------------------------------------------
        //?3.?傳統(tǒng)封裝
        function?App()?{
        ??useEffect(()?=>?{
        ????requestABC()
        ??},?[])
        ??return?(
        123
        )
        }

        async?function?requestABC()?{
        ??await?Promise.all([fetchA(),?fetchB()])
        ??await?postC()
        }

        在上面的代碼中,對(duì)生命周期中的邏輯封裝為 HookuseABC反而使其耦合了生命周期回調(diào),降低了復(fù)用性。即便我們的封裝中不包含任何 Hooks,在調(diào)用時(shí)也僅僅是包一層useEffect而已,不算費(fèi)事,而且讓這段邏輯也可以在 Hooks 以外的地方使用。

        如果自定義 Hooks 中使用到的useEffectuseState總次數(shù)不超過 2 次,真的應(yīng)該想一想這個(gè) Hook 的必要性了,是否可以不封裝。

        簡(jiǎn)單來說,Hook 要么「掛靠生命周期」要么「處理 State」,否則就沒必要。

        重復(fù)調(diào)用

        Hook 調(diào)用很「反直覺」的就是它會(huì)隨重渲染而不停調(diào)用,這要求 Hook 開發(fā)者要對(duì)這種反復(fù)調(diào)用有一定預(yù)期。

        正如上文示例,對(duì)請(qǐng)求的封裝,很容易依賴useEffect,畢竟掛靠了生命周期就能確定請(qǐng)求不會(huì)反復(fù)調(diào)。

        function?useFetchUser(userInfo)?{
        ??const?[user,?setUser]?=?useState(null)
        ??useEffect(()?=>?{
        ????fetch(userInfo).then(setUser)
        ??},?[])

        ??return?user
        }

        但,useEffect真的合適嗎?這個(gè)時(shí)機(jī)如果是DidMount,那執(zhí)行的時(shí)機(jī)還是比較晚的,畢竟如果渲染結(jié)構(gòu)復(fù)雜、層級(jí)過深,DidMount就會(huì)很遲。

        比如,ul中渲染了 2000 個(gè) li

        function?App()?{
        ??const?start?=?Date.now()

        ??useEffect(()?=>?{
        ????console.log('elapsed:',?Date.now()?-?start,?'ms')
        ??},?[])

        ??return?(
        ????

          ??????{Array.from({?length:?2e3?}).map((_,?i)?=>?({i}))}
          ????

        ??)
        }

        //?output
        //?elapsed:?242?ms

        那不掛靠生命周期,而使用狀態(tài)驅(qū)動(dòng)呢?似乎是個(gè)好主意,如果狀態(tài)有變更,就重新獲取數(shù)據(jù),好像很合理。

        useEffect(()?=>?{
        ??fetch(userInfo).then(setUser)
        },?[userInfo])???????????????????//?請(qǐng)求參數(shù)變化時(shí),重新獲取數(shù)據(jù)

        但初次執(zhí)行時(shí)機(jī)仍然不理想,還是在DidMount。

        let?start?=?0
        let?f?=?false

        function?App()?{
        ??const?[id,?setId]?=?useState('123')
        ??const?renderStart?=?Date.now()

        ??useEffect(()?=>?{
        ????const?now?=?Date.now()
        ????console.log('elapsed?from?start:',?now?-?start,?'ms')
        ????console.log('elapsed?from?render:',?now?-?renderStart,?'ms')
        ??},?[id])???????????????????????//?此處監(jiān)聽?`id`?的變化
        ??if?(!f)?{
        ????f?=?true
        ????start?=?Date.now()
        ????setTimeout(()?=>?{
        ??????setId('456')
        ????},?10)
        ??}

        ??return?null
        }

        //?output
        //?elapsed?from?start:?57?ms
        //?elapsed?from?render:?57?ms
        //?elapsed?from?start:?67?ms
        //?elapsed?from?render:?1?ms

        這也是上文為什么說useEffect設(shè)計(jì)混亂,你把它當(dāng)做 State Watcher 的時(shí)候,其實(shí)它還暗含了「初次執(zhí)行在DidMount」的邏輯。從字面意思Effect來看,這個(gè)邏輯才是副作用吧。。。

        狀態(tài)驅(qū)動(dòng)的封裝除了調(diào)用時(shí)機(jī)以外,其實(shí)還有別的問題:

        function?App()?{
        ??const?user?=?useFetchUser({??????????//?乍一看似乎沒什么問題
        ????name:?'zhang',
        ????age:?20,
        ??})

        ??return?(
        {user?.name}
        )
        }

        實(shí)際上,組件重渲染會(huì)導(dǎo)致請(qǐng)求入?yún)⒅匦掠?jì)算 -> 字面量聲明的對(duì)象每次都是全新的 -> useFetchUser因此不停請(qǐng)求 -> 請(qǐng)求變更了 Hook 內(nèi)的 State user -> 外層組件重渲染。

        這是一個(gè)死循環(huán)!

        當(dāng)然,你可以用Immutable來解決同一參數(shù)重復(fù)請(qǐng)求的問題。

        useEffect(()?=>?{
        ??//?xxxx
        },?[?Immutable.Map(userInfo)?])

        但總的看來,封裝 Hooks 遠(yuǎn)遠(yuǎn)不止是變更了你代碼的組織形式而已。比如做數(shù)據(jù)請(qǐng)求,你可能因此而走上狀態(tài)驅(qū)動(dòng)的道路,同時(shí),你也要解決狀態(tài)驅(qū)動(dòng)隨之帶來的新麻煩。

        為了 Mixin ?

        其實(shí),Mixin 的能力也并非 Hooks 一家獨(dú)占,我們完全可以使用 Decorator 封裝一套 Mixin 機(jī)制。也就是說, Hooks 不能依仗 Mixin 能力去力排眾議。

        const?HelloMixin?=?{
        ??componentDidMount()?{
        ????console.log('Hello,')
        ??}
        }

        function?mixin(Mixin)?{
        ??return?function?(constructor)?{
        ????return?class?extends?constructor?{
        ??????componentDidMount()?{
        ????????Mixin.componentDidMount()
        ????????super.componentDidMount()
        ??????}
        ????}
        ??}
        }

        @mixin(HelloMixin)
        class?Test?extends?React.PureComponent?{
        ??componentDidMount()?{
        ????console.log('I?am?Test')
        ??}

        ??render()?{
        ????return?null
        ??}
        }

        render()?//?output:?Hello,?\n?I?am?Test

        不過 Hooks 的組裝能力更強(qiáng)一些,也容易嵌套使用。但需要警惕層數(shù)較深的 Hooks,很可能在某個(gè)你不知道的角落就潛伏著一個(gè)有隱患的useEffect。

        小結(jié)

        • 本文沒有鼓吹 Class Component 拒絕使用 React Hooks 的意思,反而是希望通過細(xì)致地比對(duì)二者,從而更深入理解 Hooks。
        • React Hooks 的各種奇怪之處,也正是潛在癥結(jié)之所在。
        • 在 Hooks 之前,F(xiàn)unction Component 都是 Stateless 的,小巧、可靠但功能有限。Hooks 為 Function Component 賦予了 State 能力并提供了生命周期,使 Function Component 的大規(guī)模使用成為了可能。
        • Hooks 的「優(yōu)雅」來自向函數(shù)式的致敬,但useRef 的濫用讓 Hooks 離「優(yōu)雅」相去甚遠(yuǎn)。
        • 大規(guī)模實(shí)踐 React Hooks 仍然有諸多問題,無論是從語義理解抑或是封裝的必要性。
        • 創(chuàng)新不易,期待 React 官方之后會(huì)有更好的設(shè)計(jì)吧。

        原文鏈接:https://zhuanlan.zhihu.com/p/455317250?utm_source=ZHShareTargetIDMore&utm_medium=social&utm_oi=28389876432896

        參考資料

        [1]

        官方文檔: https://link.zhihu.com/?target=https%3A//reactjs.org/docs/hooks-reference.html%23useref

        [2]

        ?: https://link.zhihu.com/?target=https%3A//www.alt-codes.net/check-mark-symbols.php


        瀏覽 51
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美成人毛片AAAAAA | 娇妻被隔壁老王日出白浆电影 | 青娱乐亚洲自拍 | 欧美午夜伦理 | 91豆花视频入口网站 | 国产午夜探花 | 3d小舞被到爽动漫 | 啊轻点灬太粗嗯太深了用力的视频 | 女技师三级做爰按摩 | 国产porn |