LoginSignup
7
0

More than 1 year has passed since last update.

React@18 での Signals もどき

Last updated at Posted at 2022-09-10

Signals とは

Preact に Signals という新しい機能が追加されました。

Introducing Signals - PREACT
Signals - PREACT

次のような感じで、State のバケツリレー問題を Context などよりも軽やかに解決してくれそうなものです。

import { signal } from "@preact/signals"

const count = signal(0)

const Counter = () => {
  const increment = () => {
    count.value++
  }

  return (
    <div>
      <p>Count: {count.value}</p>
      <button onClick={increment}>click me</button>
    </div>
  )
}

Signals は SolidJS にも存在し、最近のトレンドといってもよいでしょう。
もしかしたら、Signals 目当てで React から Preact や SolidJS に鞍替えしたくなっている方もいらっしゃるかもしれません。

React@18 でも Signals もどきは簡単に実装できる

しかしながら、useSyncExternalStore という Hook を使えば、Signals もどきを React@18 に簡単に実装できますので、これを用いて React の中の人がどう動くかを長い目で見守ってみるのもいいかもしれません。
以下サンプルコードです。

sample.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
  </head>
  <body>
    <div id="app"></div>

    <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
  
    <script>
////////////////////////////////////////////////////

const gV = {}

const signal = iniValue => {
  const key = Symbol()
  gV[key] = {value: iniValue, fList: new Set()}
  return {
    get value() {
      return gV[key].value
    },
    set value(newValue) {
      if (gV[key].value === newValue) return;
      gV[key].value = newValue
      gV[key].fList.forEach(f => f())
    },
    _subscribe: notify => gV[key].fList.add(notify), 
    _unsubscribe: notify => gV[key].fList.delete(notify),
    _remove: () => delete gV[key],
  }
}

const useSignal = (...signals) => signals.map(item => React.useSyncExternalStore(
  // subscribe
  notify => {
    item._subscribe(notify)
    return () => {
      // unsubscribe
      item._unsubscribe(notify)
    }
  },

  // getSnapshot
  () => item.value,
))


const count1 = signal(0)
const count2 = signal(0)

const Button1 = props => {
  useSignal(count1)
  return React.createElement('button', {
    onClick: e => count1.value++,
  }, count1.value)
}

const Button2 = props => {
  useSignal(count2)
  return React.createElement('button', {
    onClick: e => count2.value++,
  }, count2.value)
}

const App = props => {
  return React.createElement(React.Fragment, {}, [
    React.createElement(Button1),
    React.createElement(Button1),
    React.createElement(Button1),
    React.createElement(Button2),
    React.createElement(Button2),
    React.createElement(Button2),
  ])
}

const root = ReactDOM.createRoot(document.getElementById("app"))
root.render(React.createElement(App))
    
////////////////////////////////////////////////////
    </script>
  
  </body>

</html>

現場からは以上です

最後までこのような拙文に付き合っていただき、まことにありがとうございました。

7
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0