🎃

StencilJSでPropは必ずしも再レンダリングしないのでWatchを併用しよう

2021/12/21に公開

Stencilでとても簡単なコンポーネントを用意したとします。 label 属性をもったボタンコンポーネントです。

@Component({
  tag: 'my-button',
  styleUrl: './my-button.scss',
  scoped: true
})
export class ButtonComponent {
  @Prop() label: string = '';

  render() {
    return (
      <button>{this.label}</button>
    );
  }
}

これは <my-button label="button"></my-button> として利用することができます。初回に表示される時、 @Prop がある状態でrenderされるので、当然レンダリングされた結果は <button>button</button> として表示されます。

では、レンダリング後に label 属性をJSでハンドリングして変更するとどうなるでしょうか。

const button = document.querySelector('my-button');
button.setAttribute('label', 'my-button')

このように変更しても my-button は再レンダリングされません。これは、 ButtonComponent としてみると this.label は値が変わっただけであるため、再度 render が走らないためです。これは常に @Prop を値レベルで監視していると変更検知が重くなり、また不要なレンダリングを行うことを防ぐためにこうなっています。

まず、変更を検知するために @Watch デコレーターが用意されています。上記コンポーネントを変更してみましょう。

  @Component({
    tag: 'my-button',
    styleUrl: './my-button.scss',
    scoped: true
  })
  export class ButtonComponent {
    @Prop() label: string = '';
+   @Watch('label')
+   async watchLabelHandler() {
+   }
  
    render() {
      return (
        <button>{this.label}</button>
      );
    }
  }

これで、 label が変更される度に、 watchLabelHandler() が実行されるようになりました。しかし、まだ render() は実行されません。 render() を実行するためには @State デコレーターのついたプロパティが変更される必要があるため、再レンダリング監視用に @State _label プロパティを用意しましょう。

  @Component({
    tag: 'my-button',
    styleUrl: './my-button.scss',
    scoped: true
  })
  export class ButtonComponent {
    @Prop() label: string = '';
+   @State() _label: string;   
    @Watch('label')
    async watchLabelHandler() {
+     this._label = this.label;
    }
  
    render() {
      return (
        <button>{this.label}</button>
      );
    }
  }

これで、 label 属性が変更される度に @Watch('label') で監視しているために watchLabelHandler() メソッドが実行され、その中で変更があると再レンダリングされる _label が変更されるために再レンダリングが走るようになりました。

簡単ですね。

それではまた。

Discussion