View State（ビューステート）とは〜対象と減らし方〜

## View State（ビューステート）とは

ページの一部を書き換えるAjax通信（VisualforceタグのreRender属性など）の際、
画面の状態を保持するために必要な情報。

実態は```input type="hidden"```のvalueに入っている暗号化された文字列。
暗号化の方法はBase64でのエンコード。

post backリクエストの際にVisualforceの状態を保持するための情報が入っている。
（post backリクエスト＝自ページへのリクエスト）

最大サイズは170KBまでとなっており、それを超えるとエラーで落ちます。

<br>

### ビューステートが作られる条件

ビューステートは```apex:form```タグがある時のみ作られます。

<br>

- 作られる時のコード（```apex:form```あり）

```
<apex:page controller="reRenderTest">
    <h1>reRenderテスト</h1>
    <apex:form id="formId">
        <apex:outputText>{!testStr}</apex:outputText>
        <apex:commandButton reRender="formId" action="{!reRenderStrAction}" value="テストボタン" />
    </apex:form>
</apex:page>
```

- 作られる```input type="hidden"```

```
<span xmlns="http://www.w3.org/1999/xhtml" id="ajax-view-state-page-container" style="display: none">
    <span id="ajax-view-state" style="display: none">
        <input type="hidden" id="com.salesforce.visualforce.ViewState" name="com.salesforce.visualforce.ViewState"
            value="エンコードされた文字列"
            autocomplete="off">
        <input type="hidden" id="com.salesforce.visualforce.ViewStateVersion"
            name="com.salesforce.visualforce.ViewStateVersion" value="数字" autocomplete="off">
        <input type="hidden" id="com.salesforce.visualforce.ViewStateMAC" name="com.salesforce.visualforce.ViewStateMAC"
            value="エンコードされた文字列"
            autocomplete="off">
        <input type="hidden" id="com.salesforce.visualforce.ViewStateCSRF"
            name="com.salesforce.visualforce.ViewStateCSRF"
            value="エンコードされた文字列"
            autocomplete="off">
    </span>
</span>
```

- 作られない時のコード（```apex:form```なし）

```
<apex:page controller="reRenderTest">
    <h1>reRenderテスト</h1>
    <form>
        <input type="text" name="test" value="test" />
    </form>
</apex:page>
```


<br>

```apex:commandButton```等のreRenderを使ってページの一部を書き換える系タグが、
```apex:form```内に存在しないとデプロイエラーになるのは、
```apex:form```のビューステートを作ってAjax時の情報保持をするのに必要だからだったんですね。


## 主なビューステートの対象

### 対象1：Visualforceコンポーネント

- Visualforceタグはビューステートサイズが増える
    - ```apex:form```の中でも外でも増える
    - reRender属性を持っていてもいなくても増える

- 標準HTMLタグ（pタグ、inputタグなど）は増えない
    - ```apex:form```の中でも外でも増えない

### 対象2：Apexの変数

- publicもprivateも同量のビューステートサイズ
- ビューステートにカウントされるのは、値が代入されている変数のみ


### 各タグのビューステートサイズ実験結果

- 量が多いので[別ページ](https://qiita.com/xQIEBo4zFWKZ0ig/items/d0fa23868fd3d5f5a711)に分離しました
- 目立って増加しているのは以下の項目でしょうか
    - Component Tree
    - Internal
- 特にInternalの増え幅が大きいですね

#### （ちなみに）internalとは

>Visualforce ページによって使用される内部 Salesforce データを表します。
開発者はこのノードを制御できません。
ビューステートサイズのうち、内部要素がどの程度を占めているかを表示するには、[State (状態)] フォルダをクリックします。

[開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.pages.meta/pages/pages_tools_edit_toolbar.htm)より

完全にピンとは来ないですが、
変数やコンポーネントのViewStateサイズと分離して表示しているあたり、
開発者が視認してるコードより更に内部的な情報（メモリとかそういうレベル）なんでしょうか。

docsには「制御できない」と書いてありますが、
[StandardSetController](https://qiita.com/xQIEBo4zFWKZ0ig/items/a5a934c05a0444dbf11c)でページネーションを実装したときの「1ページあたりのレコード表示数」によってInternalは増減するようなので、ある程度は調整が効きますね。

## ビューステートサイズの確認方法

以下の2通りがあります。

- Visualforceページから確認する方法
- 開発者コンソールから確認する方法

[具体的な手順](https://help.salesforce.com/s/articleView?id=sf.code_dev_console_tab_view_state.htm&type=5)
[タブの項目の見方](https://developer.salesforce.com/docs/atlas.ja-jp.pages.meta/pages/pages_tools_edit_toolbar.htm)

開発者コンソールからの確認は僕の環境ではできませんでしたが、
見れる内容はVisualforceページとほぼ同じなので一旦気にせず進むとします。

### ビューステートサイズを確認する際の注意点

- ビューステートサイズが制限を超えて落ちる場合、ビューステートタブも見れなくなる

一旦ビューステートサイズを削ってエラーが起きないようにし、
どこがビューステートを割合多く消費しているか確認するというデバッグ方法になります。
<br>
- ビューステートモードだとVisualforceの実行時間が以上に長くなって、CPUガバナで落ちることがある

増えてる部分がスタックログにも出てこないので、
何きっかけでCPU時間が増えているのか分からないですが、この場合一旦CPU時間を削ってのデバッグが必要になります。


## ビューステートの減らし方

### transient修飾子を付ける

transient 修飾子を付けて変数を宣言することによって、その変数をビューステートの対象外とすることができます。

ただし「ビューステートによりpost back間で値が保持される」という恩恵は受けられなくなるので、
Visualforceタブのaction属性を実行したりすると、変数がリセットされて空になります。

- 変数リセットの回避方法
    - reRenderの範囲外に記述する
        - ※あくまでviewの表示上のリセット回避で、サーバーサイドではリセットされている
    - action属性にメソッドを指定 → 指定したメソッド内で再代入する

### 変数を静的にする

静的な変数はビューステートに含まれません。
[transient キーワードの使用](https://developer.salesforce.com/docs/atlas.ja-jp.pages.meta/pages/apex_classes_keywords_transient.htm)より

### Visualforceタグの数を減らす

- VisualforceタグをHTMLの標準タグに置き換えるなど
- ```apex:repeat```などを使ってると、思ってる以上にVisualforceタグが描画されてたりするので注意が必要

>apex:inputXXXXコンポーネントは、特にapex:repeatやテーブルの中に置いた場合、多くの内部ビュー状態を発生させることがあります

[How to reduce a large internal view state / what is in the internal view state?](https://salesforce.stackexchange.com/questions/4537/how-to-reduce-a-large-internal-view-state-what-is-in-the-internal-view-state)より

### StandardSetControllerクラスを使ってページネーションを導入する

- [StandardSetControllerクラスの使い方](https://qiita.com/xQIEBo4zFWKZ0ig/items/a5a934c05a0444dbf11c)
- [超シンプルなVF・Apexのページネーション](https://qiita.com/xQIEBo4zFWKZ0ig/items/0f10af8424ac0b24b756)

ちなみにStandardSetControllerでページネーションを実装した場合、
1ページあたりのレコード数が多くても少なくても、
StandardSetControllerインスタンスのビューステートサイズは変わりません。

※ただしInternalの量は増えます

### JSで作成した変数をVisualforceにバインドする

JSで作成した変数をバインドした場合、ビューステートサイズが10分の1以下になるようです。

[SFDC：【大量データ処理】Apex開発とJavaScript開発 - ViewStateの比較](https://tyoshikawa1106.hatenablog.com/entry/2015/03/29/195835)より

### その他

- サーバーサイドのsoqlセレクト項目を減らす
- ビューステートを使わずに、カスタムオブジェクトやカスタム設定に情報を保持するように開発する
- ```apex:page```で"readOnly=true"を宣言する
![画面速度ビューステート70.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690075/b56d1190-3ace-0985-46dd-d0e1779939b6.png)

### ビューステートの表示速度への影響

[ヘルプ](https://help.salesforce.com/s/articleView?id=000323828&type=1)によると、

>一般に、ビューステートサイズが小さいほど読み込み時間が短くなります。

とのこと。

#### 実験結果

- ビューステート：2KB
- VISUALFORCE項目：0.1秒

![画面速度ビューステート2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690075/09cf0afb-7b68-0d61-bab9-c046d5ea2d6b.png)


- ビューステート：70KB
- VISUALFORCE項目：1.5秒

![画面速度ビューステート70.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690075/c3ce4e7f-5a97-297e-ce3c-911d78a0a7db.png)



VISUALFORCE項目が約15倍になっていますね。
（10KBあたり0.2秒増加）

コードの変更点は以下のみなので、増加分はビューステートの生成時間とかでしょうか。

- Visualforceにバインドしてないメンバ変数のリスト要素数を増やした
    - ※view側の描画量は全く同じ

## まとめ

ビューステートはガバナ制限としての遭遇率こそ低いですが、
Visualforceのajaxを支える根幹技術だと知って驚きました。


大きいシステムだとサイズ制限やパフォーマンス向上のために考慮が必要なので、
この記事がお役に立てれば幸いです。

## 参考文献
[[salesforce]ビューステートについて](https://deferloader.blog.uhuru.co.jp/2193/)
[ビューステートを意識したVisualforce【セールスフォース】](https://keneloper.com/visualforce-aware-of-view-state/)
[Visualforceのビューステートおよび画面間情報維持について](https://qiita.com/xuwenzhen/items/95403c138eed22cf2d85)
[[Salesforce]ビューステート](http://daeheui.com/2019/12/13/salesforce%E3%83%93%E3%83%A5%E3%83%BC%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88/)
[ビューステートの最大表示サイズ](https://salesforce.oikeru.com/entry/View_State_Limit)
[ビューステートの解消方法](http://sfsupport.blog.fc2.com/blog-entry-72.html)
[SFDC：【大量データ処理】Apex開発とJavaScript開発 - ViewStateの比較](https://tyoshikawa1106.hatenablog.com/entry/2015/03/29/195835)
[ページング使ってパフォーマンス改善](https://esp-labo.com/paging/)
[【SALESFORCE】VIEW STATEエラーのINTERNALについて](http://blog.subnetwork.jp/?p=1012)
[開発モードフッターの使用](https://developer.salesforce.com/docs/atlas.ja-jp.pages.meta/pages/pages_tools_edit_toolbar.htm)
[How to reduce a large internal view state / what is in the internal view state?](https://salesforce.stackexchange.com/questions/4537/how-to-reduce-a-large-internal-view-state-what-is-in-the-internal-view-state)