3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Riot.js v4 親/子のメソッドを簡単に呼べるプラグインを作った

Last updated at Posted at 2019-12-17

Riot.js Advent Calendar 2019 の17日目が空いていたので埋めます。
何とか全部埋まりそうです。

はじめに

Riot v4では子のメソッド、親のメソッドは簡単には呼べないようになっています。
前々回(Riot.js v4 子コンポーネントを操作する)と前回(Riot.js v4 内部プロパティまとめ)でだいぶ仕組みがわかってきました。

今回はこれを簡単に扱えるようにプラグインを作りました。
プラグインには、riot.installを使っています。

riot.install(plugin: function): Set
plugin - 生成された任意のコンポーネントの コンポーネントオブジェクト を受け取る関数

プラグイン

riot.install(component => {
  // この時点ではまだない。
  //let parent = component[Object.getOwnPropertySymbols(component).find(symbol => symbol.toString() === "Symbol(parent)")];
  if (component.hasOwnProperty("mount")) {
    let value = component["mount"];
    // コンポーネントが持っているmountを上書き可能に
    Object.defineProperty(component, "mount", {
      value,
      enumerable: false,
      writable: true,
      configurable: true
    });

    // mountにhook
    let org_mount = component.mount;
    component.mount = function (element, state, parentScope) {
      // parentがある場合
      if (parentScope) {
        component._parent = parentScope;
        element._component = component
      }
      // オリジナルのmountを実行
      return org_mount.call(this, element, state, parentScope);
    };
  }
  return component;
});

解説

riot.installで動く際に引数として与えられるcomponentには、まだmount,update,unmountしかありません。
mountを実行するとSymbolcomponentのプロパティに与えられる。

mountにhookすることでSymbolでセットしている元の変数parentScopeが取れる。

parentScopeは親のthisと同義なので、自身のコンポーネントの_parentプロパティにセット。(子->親 用)
そしてelementはマウントされたタグ要素なので、ここの_componentプロパティに自身のコンポーネントをセット。(親->子 用)

riot.installより後にマウント実行したコンポーネントが対象になります。

サンプル

index.js
import { component, register, install } from 'riot'
import App from './app.riot'
import Child from './child.riot'
import Grandchild from './grandchild.riot'

install(component => {
  // この時点ではまだない。
  //let parent = component[Object.getOwnPropertySymbols(component).find(symbol => symbol.toString() === "Symbol(parent)")];
  if (component.hasOwnProperty("mount")) {
    let value = component["mount"];
    // コンポーネントが持っているmountを上書き可能に
    Object.defineProperty(component, "mount", {
      value,
      enumerable: false,
      writable: true,
      configurable: true
    });

    // mountにhook
    let org_mount = component.mount;
    component.mount = function (element, state, parentScope) {
      // parentがある場合
      if (parentScope) {
        component._parent = parentScope;
        element._component = component
      }
      // オリジナルのmountを実行
      return org_mount.call(this, element, state, parentScope);
    };
  }
  return component;
});

register('my-child', Child);
register('my-grandchild', Grandchild);
component(App)(document.getElementById('root'));
app.riot
<my-app>
  <input type="button" value="Hello" onclick="{ call_greeting }">
  <input type="button" value="Goodbye" onclick="{ call_greeting }">
  <input type="button" value="Hola" onclick="{ direct_greeting }">
  <input type="button" value="Adios" onclick="{ direct_greeting }">
  <input type="button" value="Grandchild Change" onclick="{ call_grandchild }">
  <hr>
  <my-child message="First"></my-child>
  <hr>
  <my-child message="Second"></my-child>

  <script>
    export default {
      // 子コンポーネントのメソッドを呼び出し:対象->1つ目のmy-child
      call_greeting(e) {
        this.$("my-child")._component.greeting(e.target.value);
      },

      // 子コンポーネントのstateを直接変更:対象->2つ目のmy-child
      direct_greeting(e) {
        const child = this.$$("my-child")[1]._component;
        child.state.message = e.target.value;
        child.update();
      },

      // 孫も自分にとっては子みたいなもの:対象->2つとも書き換え
      call_grandchild(e) {
        this.$$("my-grandchild").map(elm => elm._component.greeting("Call Grandchild"))
      }
    }
  </script>
</my-app>
child.riot
<my-child>
  <p>{ state.message } World!!</p>
  <input type="button" value="First Child Change" onclick="{ call_parent }">
  <my-grandchild message="Grandchild"></my-grandchild>

  <script>
    export default {
      state: {
        message: ""
      },

      onMounted(props, state) {
        this.greeting(props.message);
      },

      greeting(msg) {
        this.state.message = msg;
        this.update();
      },

      call_parent() {
        // 親をコール
        this._parent.call_greeting({"target":{"value": "Call Parent"}});
      }
    }
  </script>
</my-child>
grandchild.riot
<my-grandchild>
  <p>{ state.message } World!!</p>
  <input type="button" value="Second Child Change" onclick="{ call_grandparent }">

  <script>
    export default {
      state: {
        message: ""
      },

      onMounted(props, state) {
        this.greeting(props.message);
      },

      greeting(msg) {
        this.state.message = msg;
        this.update();
      },

      call_grandparent() {
        // 親の親をコール
        this._parent._parent.direct_greeting({"target":{"value": "Call Grandparent"}});
      }
    }
  </script>
</my-grandchild>

t_2019_12_17_154348(slt)(raw).gif

Riotの設計思想から外れるかもしれませんが、よくあるパターンなので今後作るものはこれを使っていこうと思います。

3
1
0

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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?