Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

What is React Hooks?

React Function Component で副作用(状態、ライフサイクル)を表現する

Dan Abramov on Twitter: "If I had to condense my talk into a single frame, it would be this one. Hooks let you define your own abstractions (like useWindowWidth in this example) that “feel” as natural to use as React’s built-in features.… https://t.co/IdV4inLcZm"

概要

Hooks at a Glance – React

React Hooks での状態管理と副作用の表現 - mizchi's blog

影響

https://github.com/palmerhq/the-platform

mweststrate/use-immer: Use immer to drive state with a React hooks

yesmeck/react-with-hooks: Ponyfill for the proposed React Hooks API

Mark Dalgleish @ ReactiveConf 🇨🇿 on Twitter: "Hooks, coming soon to a framework near you.… "

recompose

acdlite/recompose: A React utility belt for function components and higher-order components.

I will be discontinuing active maintenance of this package (excluding perhaps bugfixes or patches for compatibility with future React releases), and recommending that people use Hooks instead.

react-redux

Provide React Hooks · Issue #1063 · reduxjs/react-redux

どう対応するか議論中

redux には影響なく、react-redux がどう表現するか、という話になっている。元々 Context API の変更の対応が必要になっており、議論は必要。

React Hooks は中で何が起こっているか

Dan 先生曰く、中身は Suspense と一緒なので、Suspense の非同期APIをどう実装されているかで解説する

こんなコードを考える。

const Foo = () => {
  const data = readData();
  return <pre>{JSON.stringify(data)}</pre>
};
const el = (
  <React.Suspense fallback="loading...">
    <Foo />
  </React.Suspense>
);

コードの見た目上は同期だが、実際には非同期に描画される。

この fetchData は何をしているか、こう表現することもできる

let data = null
const Foo = () => {
  if (data == null) {
    throw new Promise(async resolve => {
      data = await fetch('/my-data').then(res => res.json());
      resolve();
    })
  }
  return <pre>{JSON.stringify(data)}</pre>
}

この throw された Promise は Error Boundary でハンドルされる

Error Boundaries – React

class MySuspense extends React.Component {
  state = { hasError: false, data: null }
  async componentDidCatch(error) {
    this.setState({ hasError: true })
    const data = await error;
    this.setState({ hasError: false, data})
  }
  render() {
    if (this.state.hasError) {
      return "Loading...";
    } else {
      return <Foo {...this.state.data} />
    }
  }
}
  • Foo を render しようとする
  • 未初期化なので例外が発生し、promise が throw される
  • ErrorBoundary の componentDidCatch(error) で 子の render スコープの例外がキャッチされる
  • ErrorBoundary が promise を解決すると、再び Foo の render が行われる
  • Foo の描画が成功

正確には Suspense には描画を間引く機能もある。(TimeSlicing)

このような挙動を本質的には Algebraic Effects というらしく、「継続を取ってこれる例外」とのこと。

m2ym on Twitter: "React hooks、本質的には algebraic effects らしい。React はもはや言語処理系といっても過言ではないレベルで、言語処理系が持っている機能を含み始めている。「フレームワークではなくライブラリ」という触れ込みは何だったのか"

m2ym on Twitter: "Algebraic effects の実装には限定継続のようなものが必要なのだけど、 JavaScript でどうやっているのかよくわからない。 Promise を例外で投げればそれっぽいことはできそうだけど。"

Dan Abramov on Twitter: "Like throwing a Promise (with Suspense), this implementation is a pragmatic compromise that keeps most of the benefits of the ideal model without making the API feel noisy and heavy-handed."

React のドメイン知識として、 throw new Promise(...) は Algebraic Effects の為のイディオムと知っておくといいかもしれない。

追記: Algebraic Effectsとは? 出身は? 使い方は? その特徴とは? 調べてみました! - Qiita

今後の予想

  • コアチームが renderProp 推しなので、HOCが推奨されなくなりそう。(recompose の acdlite 氏は Suspense 実装者の一人)
  • React.Component のクラス API はあんまり使ってほしくなさそう。すべてが Function Component へ
  • Redux は Component の外にあるデータの集約を行う層なので、これによって Redux がなくなるわけではない。ただし connect => mapStateToProps の HOC 的な表現でなくなる可能性は高い

個人的に、 class API は間違ったOOPユーザーへの割れ窓になりがち + 元々 HOC は型推論が難しいという問題があったので、この流れには賛成。

参考

@koichik

This comment has been minimized.

Copy link

commented Oct 30, 2018

@mizchi

This comment has been minimized.

Copy link
Owner Author

commented Oct 30, 2018

@koichik oops

@uehaj

This comment has been minimized.

Copy link

commented Nov 1, 2018

「このような挙動を本質的には Algebraic Effects というらしく、「継続を取ってこれる例外」とのこと。」

https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889
わたしが読み間違えているかもですが、少なくとも

https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889

Hooks could be implemented in a pure way using algebraic effects (if JavaScript supported them).
Finally, if you’re a functional programming purist and feel uneasy about React relying on mutable state as an implementation detail, you might find it satisfactory that handling Hooks could be implemented in a pure way using algebraic effects (if JavaScript supported them). And of course React has always relied on mutable state internally — precisely so that you don’t have to.

の文脈では、(hooksは完全なる(大域的な)副作用コードだけど)、副作用が大嫌いな純粋関数型の信奉者の人のために心の慰めとして言えば、もし将来JavaScriptがalgebraic effectsを実装したら、そのeffectを使って純粋関数として実装できるよ、ということではないかと。とはいえ、何の慰めにもなっていないとは思います(私見ですが)。現React Hooksの理解にはほとんど寄与しないと言っていいと思います(たとえループが再帰で表現できたとしても、ループは再帰ではないのと同じ)。

(以下は余談です)
私の理解では、React Hooksは非クラスベースオブジェクト指向の実現手段です。いままで、「コード + データ(状態)」という関係性を、クラスベースオブジェクト指向では当然「クラス」という単位で表現してしまっていた。でも、その「クラス」機能は汎用的なので、Reactでの画面コンポーネントの実現には最適ではないかもしれない。たとえば、ライフサイクルメソッドシグネチャはインターフェースで決められ増減は自由ではない。
ES2015のクラス定義(もしくはCoffeScript由来のイディオム)の採用をやめて、いったん分解して、SFCなる関数という単位で画面コンポーネントを表現してみる、stateのためのメモリ領域はマウントされたコンポーネントツリー側に、仮想DOMレンダラが上にわりあてる。やってみたら実際わるくない。
つまり、クラスやprototypeを使わずに、関数と状態を結びつける方法を、Reactの仮想DOMレンダラーという文脈でゼロベースで考えると、合理的なものができたった。

つまりRect Hooksは「クラスベースもprototypeも使用しないオブジェクト指向」(を含む)ということです。たとえば、

function Component() {
  const [name, setName] = useState('Mary');
  const [age, setAge] = useState(16);
   :
}

これを、

class Component extends React.Component {
    name = 'Mary'; // field
    void setName(name) { this.name = name; }
    age = 16; // field
    void setAge(age) { this.age = age; }
    render(){ 
  :
    }
}

というクラス定義の代替として使おうということ。これを実現するには大域状態とhooks呼び出し順序が重要で、そのためにRules of Hooksが必要になります。
まとめるとHooksは、仮想DOMレンダラの管理配下で、newやthis、prototype、this経由のメソッド呼び出し、継承が除去されたオブジェクト指向の再実装を含んだものです(もともとそこいらはJSの中でもごちゃごちゃしてたところなので、すっきり)。
(もちろんそれだけではなく、Reactコンポーネントの管理にまつわるさまざまな機能を実現できる)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.