偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Vue 中沒有閉包陷阱,但為此付出了什么

開發(fā) 前端
在社區(qū)里,部分 Vue 使用者,會常常因?yàn)?React 中存在閉包陷阱,而認(rèn)為 Vue 是一個更加優(yōu)秀的框架。個別極端的 Vue 使用者,還會因此而貶低 React,認(rèn)為閉包陷阱是 React 的一個設(shè)計(jì)缺陷。

今天聊一個非常有爭議的話題。

在社區(qū)里,部分 Vue 使用者,會常常因?yàn)?React 中存在閉包陷阱,而認(rèn)為 Vue 是一個更加優(yōu)秀的框架。個別極端的 Vue 使用者,還會因此而貶低 React,認(rèn)為閉包陷阱是 React 的一個設(shè)計(jì)缺陷。

那么,這真的是 React 的設(shè)計(jì)缺陷嗎?

Vue 中沒有閉包陷阱,那它是否也為此付出了什么代價(jià)呢?

我們一點(diǎn)點(diǎn)來分析一下

1.前置知識

我們來思考一個場景。在一個單獨(dú)的模塊 A.js 中定義一個變量。

// A.js
let a = 20

然后在模塊 B.js 中,我們想要訪問這個變量 a,并且能夠修改這個變量 a 的值,應(yīng)該怎么辦呢?

// B.js
import A from './A.js'

// 如何訪問模塊 A 中的變量 a

我們發(fā)現(xiàn)無法直接訪問,因此,我們通常的做法是在模塊 A 中,導(dǎo)出一個專門用于訪問 變量 a 的函數(shù),和一個專門用于修改 變量 a 的函數(shù)。

// A.js
let a = 20
export function getA() {
  return a
}
export function setA(value) {
  a = value
}

然后我們在模塊 B 中,就可以調(diào)用 getA 函數(shù)來訪問變量 a,也可以調(diào)用 setA 函數(shù)來修改變量 a 的值。

// B.js
import { getA, setA } from './A.js'

const value = getA()
console.log(value)
setA(30)
// 此時(shí) value 的值會變成 30 嗎?

此時(shí),我們就遇到一個經(jīng)典問題:當(dāng)我調(diào)用了 setA 修改了 A 的值之后,上面代碼中的 value 的值會發(fā)生變化嗎?

正確答案是:不會。

這就有意思了,為什么 value 的值不會發(fā)生變化呢?

這是因?yàn)槲覀兺ㄟ^ getA 函數(shù)訪問的是變量 a 的值,而不是變量 a 的引用。因此,如果想要得到新的值,我們還需要重新調(diào)用一次 getA 函數(shù)。

// B.js
import { getA, setA } from './A.js'

const value = getA()
console.log(value)
setA(30)
// 此時(shí)得到最新值
const value2 = getA()
console.log(value2) // 30

那我們能不能不通過調(diào)用 getA 函數(shù),就能夠直接訪問到變量 a 的值呢?

答案是不行。

現(xiàn)在,我們對這種傳統(tǒng)的方式進(jìn)行兩種思路的調(diào)整。

第一種是稍作修改,模仿成 React 語法的樣子。

// A.js
let a = 20

// 充當(dāng)了 get 的角色
function useState() {
  return [a, setA]
}

function setA(value) {
  a = value
}
// B.js

import { useState } from './A.js'

const [a, setA] = useState()
console.log(a)
setA(30)
console.log(a) // 20

我們會發(fā)現(xiàn),這個情況,就跟 React 中,我們修改了 state 值之后,無法直接訪問到最新的 state 值一樣了。

所示我經(jīng)常說,無法獲取到最新值,不是 React 的設(shè)計(jì)缺陷,而是 JS 語言特性他就是如此。

第二種,我們可以通過重新定義 a 的類型,來避免使用 getA 才能訪問新值。

重新修改 A.js 模塊,代碼如下所示:

// A.js

let a = {
  value: 20
}

// 充當(dāng) get 的角色
export function ref() {
  return a
}
// B.js

import { ref } from './A.js'

const a = ref()
console.log(a.value)
// 充當(dāng) set 的角色
a.value = 30
// 此時(shí)通過 .value 訪問到最新值
console.log(a.value) // 30

此時(shí),由于我們拿到的直接是一個引用類型,因此,我們可以通過 .value 的方式,做了一個訪問的動作,從而得到最新的值。

此時(shí),我們就可以發(fā)現(xiàn),雖然上面的代碼演變,一直都是框架無關(guān)的,但是,我們只需要稍作調(diào)整,就可以幾乎完全一致的分別還原 React 與 Vue 的語法。

2.Vue 付出的代價(jià)是什么?

接下來,我們要思考的是,當(dāng)我們通過調(diào)整變量的類型結(jié)構(gòu),把基礎(chǔ)類型包裝成引用類型之后,Vue 為此付出了什么代價(jià)?

首先一個很明顯的代價(jià)就是:語義不一致。

在 Vue 中,當(dāng)我們使用 ref 定義一個響應(yīng)式狀態(tài)時(shí),認(rèn)為這個狀態(tài)應(yīng)該是一個基礎(chǔ)類型。但是實(shí)際上,我們拿到的是一個引用類型。

通過 ref 傳入的基礎(chǔ)類型必須包裹到一個引用類型中,才能讓能力變得正常。

因此,我們必須使用 .value 的方式來訪問最新值。

不少開發(fā)者會覺得這種方式不夠優(yōu)雅。

所以,在某個階段,Vue 團(tuán)隊(duì)也曾經(jīng)試圖解決這個問題,并提出了如下這種方案。

let count = $ref(0)

// 直接訪問,無需 .value
console.log(count)

function increment() {
  count++
}

這種方式是通過在編譯時(shí),自動添加 .value 的方式來訪問最新值。但是最終由于要解決的問題更多,還是放棄掉了這種方案,ref 也被擴(kuò)展到可以傳入對象,并被官方團(tuán)隊(duì)作為推薦使用。

其次,由于語義的不一致,.value 的使用,在 template、watch、深層監(jiān)聽、 組件傳參 等問題中,用法也不一樣,比較混亂。

如下所示:

const x = ref(0)
const y = ref(0)

// 不用 .value
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// 使用 .value
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 不用 .value 與 使用 .value 混用
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

假如你是一名 Vue 新玩家,看到這樣的使用場景,你會不會感覺有點(diǎn)懵?

于是,在使用 Vue 的時(shí)候,有的同學(xué)老有一種我使用的這個值,到底有沒有被監(jiān)聽到、還有沒有響應(yīng)性的心理負(fù)擔(dān)存在。

事實(shí)上,為了與 React 在底層的實(shí)現(xiàn)保持差異,Solidjs 在語法上也付出了與 Vue 類似的代價(jià)。

如下所示,是一個 Solidjs 的案例。

const CountingComponent = () => {
  const [count, setCount] = createSignal(0);
  const interval = setInterval(
    () => setCount(count => count + 1),
    1000
  );
  onCleanup(() => clearInterval(interval));
  return <div>Count value is {count()}</div>;
};

這里我們要非常關(guān)注的是,狀態(tài) count 返回的不是一個值,而是一個函數(shù)。他雖然不需要通過 .value 去獲取最新值,但是他需要通過返回一個函數(shù),并通過調(diào)用該函數(shù)的方式,才能得到最新值。

所以他使用的時(shí)候就變成這個樣子了。

<div>Count value is {count()}</div>

但是與此同時(shí),他的 set 方法中回調(diào)函數(shù)的參數(shù),又不是一個函數(shù),而是一個狀態(tài)值,所以寫法就與外面的 count() 不一致。

setCount(count => count + 1)

// or
setCount(count() + 1)

也正因?yàn)槿绱?,Vue 語法不一致、狀態(tài)易丟失響應(yīng)性的坑,Solidjs 一個也避免不了。特別是在組件傳 props 時(shí),迷惑性很強(qiáng)。也是采用了一堆語法糖來修修補(bǔ)補(bǔ)。

這就是代價(jià)。

所以當(dāng)你覺得 React 的閉包陷阱,是一個設(shè)計(jì)缺陷的時(shí)候,不妨也想想 Vue 和 Solidjs 為了不出現(xiàn)閉包陷阱,都付出了什么樣的代價(jià),也許你會有不一樣的答案。

我的觀點(diǎn)是,并不存在誰的設(shè)計(jì)理念更先進(jìn),這只是在沒有完美方案之下的權(quán)衡而已。

3.React 是如何思考的?

實(shí)際上,在 React 中,也有通過訪問引用類型的方式,直接獲取值的語法,這就是 useRef()。

const count = useRef(0)

// 通過 .current 訪問最新值
count.current

但是區(qū)別就是,我們使用 useRef 定義的值,不具備響應(yīng)性,他只是一個普通的 JS 變量,不與組件狀態(tài)綁定。那為什么 React 要這樣做呢?

React 基于一個很重要的原則:使用 useState 定義的值,只與組件狀態(tài)綁定,而使用 useRef 定義的值,則僅參與邏輯運(yùn)算,不與組件狀態(tài)綁定,更新時(shí)也不影響組件的重新渲染。

而狀態(tài)值的更新,會引發(fā)組件重新執(zhí)行,此時(shí) useState 就會自然得到一次執(zhí)行機(jī)會,從而獲取到最新值。

因此,在理想情況下,如果使用者能夠正確分清楚:哪些是狀態(tài)值,哪些是邏輯值,就能極大的避免需要獲取最新值的場景出現(xiàn)。

但是麻煩的地方就在于,一部分 React 開發(fā)者由于自學(xué)的緣故,所以并沒有意識到應(yīng)該去區(qū)分狀態(tài)的屬性問題。于是就有一種情況出現(xiàn),他們在項(xiàng)目中,會瘋狂濫用 useState,定義任何變量都是 useState。

這種情況之下,閉包陷阱就非常容易出現(xiàn)。

4.React 中更麻煩的情況

前面我們提到了要區(qū)分狀態(tài)值和邏輯值,但是這個時(shí)候,會存在一個更麻煩的情況,那就是,在少部分情況下,有一個狀態(tài),他他既是狀態(tài)值,又是邏輯值,事情就麻煩了。

這就會非常容易導(dǎo)致閉包陷阱的產(chǎn)生。就如這個案例的 increment 變量,他既是狀態(tài)值,又是邏輯值。

面對這種情況,我們通過將該狀態(tài)值一分為二的方式來解決,分別定義一個狀態(tài)值,一個邏輯值。如下所示:

import { useState, useEffect, useRef } from 'react';
import Button from 'components/ui/button';

export default function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);
  const incrementRef = useRef(1);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + incrementRef.current);
    }, 1000);
    return () => {
      clearInterval(id);
    };
  }, []);

  function incrementHandler() {
    setIncrement(i => i + 1);
    incrementRef.current += 1;
  }

  function decrementHandler() {
    setIncrement(i => i - 1);
    incrementRef.current -= 1;
  }

  function resetHandler() {
    setCount(0);
  }

  return (
    <div className='p-4'>
      <div className='flex items-center justify-between'>
        <div className='text-2xl font-bold font-din'>
          Counter: {count}
        </div>
        
        <Button onClick={resetHandler}>Reset</Button>
      </div>
      <hr />
      <div className='flex items-center gap-2'>
        Every second, increment by:
        <Button disabled={increment === 0} onClick={decrementHandler}>–</Button>
        <span className='text-lg font-din'>{increment}</span>
        <Button onClick={incrementHandler}>+</Button>
      </div>
    </div>
  );
}

我們希望他以邏輯值的身份參與到 useEffect 的回調(diào)函數(shù)中,而不是以狀態(tài)值的身份去添加到依賴項(xiàng)中。

因此,在過往的解決方案中,我們?yōu)榱死@開閉包陷阱,但是又不想把 increment 作為依賴項(xiàng),我們就會把這個變量一分為二,分別定義一個狀態(tài)值,一個邏輯值。

// 狀態(tài)值驅(qū)動 UI 變化
const [increment, setIncrement] = useState(1);

// 邏輯值參與 useEffect 的回調(diào)函數(shù)邏輯運(yùn)算
const incrementRef = useRef(1);

然后在更新時(shí),保證狀態(tài)值與邏輯值的同步更新。

setIncrement(i => i + 1);
incrementRef.current += 1;

這樣,我們就可以保證在 useEffect 的回調(diào)函數(shù)中,使用的 increment 值始終是最新的值,又不用把 increment 作為依賴項(xiàng)。

5.總結(jié)

很顯然,在如何訪問到最新值上面,Vue 和 React 做了不一樣的選擇。但是,也并不是沒有付出任何代價(jià)。Vue 在語法上付出了語義不一致的代價(jià),React 在邏輯上付出了需要區(qū)分狀態(tài)值和邏輯值的代價(jià)。

兩種方案都不完美,這只是一種根據(jù)實(shí)際情況做出的選擇,而不存在誰一定比誰更好,誰一定就是最優(yōu)解的說法。

對于我個人而言,我更傾向于 React 的選擇。這是因?yàn)?,隨著我們對 React 的理解越來越深,我可以通過提高自己個人開發(fā)能力的方式合理的區(qū)分狀態(tài)值與邏輯值,從而避免閉包陷阱的產(chǎn)生。

但是 Vue/solidjs 語義不一致的問題,卻永遠(yuǎn)都會存在。

如果是你,你會更傾向于哪種方案呢?

責(zé)任編輯:姜華 來源: 這波能反殺
相關(guān)推薦

2022-05-04 10:38:58

React閉包組件

2024-01-08 08:35:28

閉包陷阱ReactHooks

2019-05-10 08:30:09

人臉識別

2019-11-07 21:51:18

閉包前端函數(shù)

2012-09-13 10:40:30

技術(shù)債務(wù)管理項(xiàng)目管理

2022-05-05 08:31:48

useRefuseEffecthook

2021-01-13 11:25:12

JavaScript閉包函數(shù)

2022-09-02 17:26:18

Golang閉包

2024-01-22 09:51:32

Swift閉包表達(dá)式尾隨閉包

2023-11-02 08:53:26

閉包Python

2020-08-11 09:47:30

JS閉包代碼

2016-11-01 09:18:33

Python閉包

2012-11-29 10:09:23

Javascript閉包

2019-04-15 13:32:53

5G機(jī)器人物聯(lián)網(wǎng)

2011-05-12 18:26:08

Javascript作用域

2011-08-24 17:09:35

LUA閉包函數(shù)

2021-10-26 13:18:52

Go底層函數(shù)

2021-02-21 16:21:19

JavaScript閉包前端

2021-08-25 07:56:37

MySQLMyRocks存儲

2025-03-28 00:45:00

JavaScript閉包React
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號