· front end · 9 min read

遇見 Signal 就擁抱 Angular 的 React 愛好者

在學 signal 的時候做的小筆記

在學 signal 的時候做的小筆記

一開始是在 ExplainThis 全端開發雙週報 #19 看到:

而在 2024 年,Ryan 仍然預測 Signals 會是討論重點,因為 Solid.js 而流行起來的 Signals,現在可以在 Vue 的 Vue Vapor,以及 Svelte 的 Svelte 5 都看見 Signals 的身影,可以預見在 2024 會有更多框架導入 Signals 的概念。

聽了 signal 這個名詞好一陣子,所以決心好好的研究一下。先自我介紹一下,一開始學的是 react ,最近工作開始用 angular 做開發,所以搜尋資料的時候會好奇跟聯想的都會跟這兩個框架有關。

先說結論。signal 給我的感覺很微妙,本來以為學 angular 會讓我逐漸跟 react 的生態系脫鉤,但沒想到反而讓我有一些特別的觀察。說到底前端就都是 js 嘛,框架真的無所謂,最後都會殊途同歸。BTW 會寫 React 的人之後學 Angular 摩擦力又更小了,反之…我就沒那麼確定了呵呵~

Signal 是什麼?

先從 React 開始查,畢竟是我最熟悉的框架,而且聽同事說好像快被 Solid.js 打趴,搞得我很不爽,笑死?我大React會輸??? 很好奇。

Qwik 和 Angular 的作者 Miško Hevery useSignal() is the Future of Web Frameworks 有解釋了 signal 跟原生 react 的api 到底有什麼差別。

Hookreturn
useState()value + setter
useSignal()getter + setter

主要就是差在 consume state 的時候到底是讀 value 本身,還是 call getter 再取得值。

其他 signal 套件的原理基本上都是這樣。

useSate 怎麽了嗎?

這邊作者就提到 useState的問題就在於,當 consumer 是使用值的時候其實 app 根本不會知道這個值會在哪裡被使用。

The passing of state-value does not give the signal any information about where the value is actually used.

所以當 consumer 是使用 state-value 時, app 根本不會知道這個值會在哪裡被使用,所以會去監聽每個 component 的 props 跟 states 一旦偵測到變化就是整個 component rerender。換句話說就是 useSate 觸發 rerender 的範圍太大了。

假設 RootComponent 又包含了很多 SubComponent。

    const RootComponent = ()=>{
        const [count,setCount] = useSate(0)
        return (
            <RootComponent>
                <SubComponentA count={count}/>
                <SubComponentB/>
                <SubComponentC/>
            </RootComponent>
        )
    }

RootComponent,SubComponentA,B,C 也都會觸發 rerender,可想而知會是個不小的計算量(這就是我們為什麼需要 virtual DOM 嗎?)。

useSignal 有解?

useSignal 解法是在 call getter 的時候,會幫 call getter 的 component 建立一個 subscription。當 setter 被調用之後,就可以指向有 subscribe 的 component 去做更新。

    const RootComponent = ()=>{
        const [getCount,setCount] = useSignal(0)
        return (
            <RootComponent>
                <SubComponentA getCount={getCount}/>
                <SubComponentB/>
                <SubComponentC/>
            </RootComponent>
        )
    }

    const SubComponentA= ({getCount})=>{
        return (
            <p>{getCount()}</p>
        )
    }

所以只會去 rerender SubComponentA。這就是很多文章提到的顆粒度。

不過針對渲染的效能問題,React 其實有提供很多優化的 API,像是 UseMemo或是 memo 之類的。大家還有疑問的話可以回去看 原文,然後他會跟你說,就算優化了還是無法盡善盡美。

那 virtual DOM ?

我自己看完是覺得如果是這樣的話 virtual DOM 的效益會不會就沒有以前來得大了,有查到 preact 的官網在介紹 signal 的時候有提到他們直接 bypass virtual DOM

Can we go even faster?

… Better still, what if we bypassed the Virtual DOM entirely and updated the text directly in the DOM? …So yeah, we did that too. You can pass a signal directly into the JSX anywhere you’d normally use a string. The signal’s value will be rendered as text, and it will automatically update itself when the signal changes. This also works for props.

但是有相關討論的文章不多,再加上我自己也沒好好看過 virtual DOM 的原始碼所以不敢肯定,希望大家可以幫我解答這塊。

Observable Pattern?

看到這裡我就在想,這不就是觀察者模式嗎。之前在學 Angular 的時候因為他深度綁定 RxJS,所以花了一點力氣去暸解,沒想到就派上用場了。有興趣大家可以看這篇 黃升煌 Mike 所寫的 觀察者模式 Observable Pattern 認識 RxJS,例子很形象。

我個人認為這個模式最重要概念的是:「當我們要觀察一個資料的變化時,與其主動去關注它的狀態,不如由觀察的目標主動告知我們資料變化,可以更加即時且可靠的得知資料變化。」

套用到我自己以前的經驗,就很像在 fetch 資料的時候為了要確保是即時資料。那與其定義了一堆 trigger 資料更新的 function( CRUD、定時更新…),還不如就直接 web socket。

基本上 signal 的寫法就是 RxJS 裡面的 BehaviorSubject 的簡化版。 所以我就想,Angular 應該很容易就可以整合這樣的寫法吧?去找找看有沒有套件可以做到。結果官方早就實作了。

Signal in Angular

看完官方文件之後我其實滿開心的,這寫法也太 react 了吧!

State

// Angular
@Component({
  standalone: true,
  selector: 'pagination',
  template: `
        <div>
          <p>Count: {count()}</p>
          <button (click)={onClick()}}>click me</button>
          <button (click)={onClickTwo()}}>click me</button>
        </div>
    `,
})
class Counter{
  count = signal(0);
  onClick (){
    this.count.set(this.count()++);
  }
  onClickTwo (){
    this.count.update(currentCount => currentCount + 1);
  }
}

// react
function Counter() {
    const [count,setCount] = useState(0);
    const onClick = () => {
        setCount(count++)
    }
    const onClickTwo = ()=>{
        setCount(currentCount => currentCount + 1);
    }
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={onClick}>click me</button>
    </div>
  );
}

Effect

// Angular
@Component({
  standalone: true,
  selector: 'pagination',
  template: `
        <div>
          <p>Count: {count()}</p>
          <button (click)={onClick()}}>click me</button>
        </div>
    `,
})
class Counter{
  count = signal(0);
  doubleCount = computed(() => count() * 2);

  constructor(){
    effect(() => {
      console.log(`The current count is: ${count()}`);
    });
  }

  onClick (){
    this.count.set(this.count()++);
  }
}

// react
function Counter() {
    const [count,setCount] = useState(0);
    const [doubleCount,setDoubleCount] = useState(0);

    useEffect(()=>{
        setDoubleCount(count * 2);
        console.log(`The current count is: ${count}`);
    },[count])

    const onClick = () => {
        setCount(count++)
    }
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={onClick}>click me</button>
    </div>
  );
}

Clean up

//Angular
@Component({
  standalone: true,
  selector: 'effect-component',
  template: `<div></div>`,
})
class EffectComponent{
  user = signal(0);

  constructor(){
    effect((onCleanup) => {
      const timer = setTimeout(() => {
        console.log(`1 second ago, the user became ${user()}`);
      }, 1000);

      onCleanup(() => {
        clearTimeout(timer);
      });
    });
  }
}

// React
const EffectComponent = ()=>{
    const [user,setUser] = useSate(0);

    useEffect(() => {
        const timer = setTimeout(() => {
            console.log(`1 second ago, the user became ${user}`);
        }, 1000);
        return clearTimeout(timer);
    },[user]);

    return (<div></div>)
}

應該是因為一開始Solid.js從 react 生態系出來的,所以開發者是有意識地在降低 react 使用者的學習成本。到了現在 signal 就是一個概念上偏向 angular,但是使用上是 react 的東西。我就是在這時候突然感受到前端分框架其實很無聊。

心得

Angular 原本的 change detect 是依賴 zone.js 跟 Signal 也是不一樣,zone.js 之於 angular,就像 virtual dom 之於 react。signal 有點融合的兩家之長的感覺。

不過相較於 Angular 裡面還有 toObservable,toSignal 這種很容易跟舊有 code 整合的 method。React 跟 signal 就沒那麼合了?

Signal:更多前端框架的选择 這篇文章的最後有找到一個 React team 的成員發表的看法

反正對於我這種平民而言,各個大神都有自己的想法真是太好了,你們盡量去試吧!反正最後受益就是我們這些消費者(?

最後稍微提一下,認真覺得 Angular 越來越 react 了,從之前的 standalone component 加上現在的 signal,會寫 React 的人之後學 Angular 就真的只剩 RxJS 了

Back to Blog

Related Posts

View All Posts »
ORC 撞牆實錄

ORC 撞牆實錄

一個只會寫 javascript 的前端學習怎麼用AI 幫自己賦能的筆記