edit と save

ブロックの登録では edit 関数と save 関数を使用してブロックがどのように動作し、操作、保存されるかのインターフェイスを提供します。

edit

edit 関数はエディター内でのブロックの構造を記述します。ブロックが使用される際、エディターがどのようにブロックをレンダリングするかを表します。

ESNext

edit: () => {
    return <div>Your block.</div>;
}

ES5

// A static div
edit: function() {
    return wp.element.createElement(
        'div',
        null,
        'Your block.'
    );
}

edit 関数はオブジェクト引数を通じて以下のプロパティを受け取ります。

attributes

attributes プロパティはすべての利用可能な属性と対応する値を表します。属性はブロックタイプ登録の際に attributes プロパティで記述されます。属性ソースを指定する方法については属性のドキュメントを参照してください。

この例ではブロック登録の際に content 属性を定義したと仮定し、edit 関数内で値を受け取って使用します。

ESNext

edit: ( { attributes } ) => {
    return <div>{ attributes.content }</div>;
}

ES5

edit: function( props ) {
    return wp.element.createElement(
        'div',
        null,
        props.attributes.content
    );
}

エディターにブロックを挿入すると div の中に attributes.content の値が表示されます。

className

className プロパティはラッパー要素のクラス名を返します。クラス名は save メソッドでは自動的に追加されますが、edit では追加されません。これはルートの要素が、ブロックのビジュアル部分を司るメインの要素と異なるかもしれないからです。関数内でどの要素に追加すべきかをリクエストできます。

ESNext

edit: ( { attributes, className } ) => {
    return <div className={ className }>{ attributes.content }</div>;
}

ES5

edit: function( props ) {
    return wp.element.createElement(
        'div',
        { className: props.className },
        props.attributes.content
    );
}

isSelected

isSelected プロパティはブロックが現在選択されているかどうかを伝えるオブジェクトです。

ESNext

edit: ( { attributes, className, isSelected } ) => {
    return (
        <div className={ className }>
            Your block.
            { isSelected &&
                <span>Shows only when the block is selected.</span>
            }
        </div>
    );
}

ES5

edit: function( props ) {
    return wp.element.createElement(
        'div',
        { className: props.className },
        [
            'Your block.',
            props.isSelected ? wp.element.createElement(
                'span',
                null,
                'Shows only when the block is selected.'
            )
        ]
    );
}

setAttributes

ブロックは setAttributes 関数を使用して、ユーザーの操作に基づき個々の属性を更新できます。

ESNext

edit: ( { attributes, setAttributes, className, isSelected } ) => {
    // Simplify access to attributes
    const { content, mySetting } = attributes;

    // Toggle a setting when the user clicks the button
    const toggleSetting = () => setAttributes( { mySetting: ! mySetting } );
    return (
        <div className={ className }>
            { content }
            { isSelected &&
                <button onClick={ toggleSetting }>Toggle setting</button>
            }
        </div>
    );
}

ES5

edit: function( props ) {
    // Simplify access to attributes
    let content = props.attributes.content;
    let mySetting = props.attributes.mySetting;

    // Toggle a setting when the user clicks the button
    let toggleSetting = () => props.setAttributes( { mySetting: ! mySetting } );
    return wp.element.createElement(
        'div',
        { className: props.className },
        [
            content,
            props.isSelected ? wp.element.createElement(
                'button',
                { onClick: toggleSetting },
                'Toggle setting'
            ) : null
        ]
    );
},

オブジェクトや配列の属性を使用する場合には、更新の前に属性をコピーするかクローンしてください。

ESNext

// Good - a new array is created from the old list attribute and a new list item:
const { list } = attributes;
const addListItem = ( newListItem ) => setAttributes( { list: [ ...list, newListItem ] } );

// Bad - the list from the existing attribute is modified directly to add the new list item:
const { list } = attributes;
const addListItem = ( newListItem ) => {
    list.push( newListItem );
    setAttributes( { list } );
};

ES5

// Good - cloning the old list
var newList = attributes.list.slice();

var addListItem = function( newListItem ) {
    setAttributes( { list: newList.concat( [ newListItem ] ) } );
};

// Bad - the list from the existing attribute is modified directly to add the new list item:
var list = attributes.list;
var addListItem = function( newListItem ) {
    list.push( newListItem );
    setAttributes( { list: list } );
};

コピーやクローンが必要なのはなぜでしょうか ? JavaScript では配列やオブジェクトは参照渡しされるため、コピーやクローンを行うことで変更が同じデータへの参照を持つ他のコードに影響を与えないことが保証されます。さらに Gutenberg プロジェクトは Redux ライブラリの哲学、state は不変でなければならない に従っています。データは直接変更せず、変更を含む新しいバージョンのデータを作る必要があります。

save

save 関数は、最終的なマークアップに異なる属性を結合する方法を定義します。この属性は post_content 内にシリアライズされます。

ESNext

save: () => {
    return <div> Your block. </div>;
}

ES5

save: function() {
    return wp.element.createElement(
        'div',
        null,
        'Your block.'
    );
}

ほとんどのブロックで save の戻り値は、ブロックがサイトのフロントエンドでどのように表示されるかを示す WordPress Element のインスタンス になります。

注意: save から文字列値を返すことができますが、この値は_エスケープされます_。文字列が HTML マークアップを含む場合、サイトのフロントエンドには、同等の HTML ノードコンテンツではなくマークアップがそのまま表示されます。save から生の HTML を返す必要がある場合は wp.element.RawHTML を使用してください。名前が示すとおり、これは クロスサイトスクリプティング が発生しやすいため、可能な場合は WordPress Element 階層の使用を推奨します。

注意: save 関数は、呼び出し時に使用された属性にのみ依存する純粋関数でなければなりません。どのようなサイドイフェクトも与えられず、別のソースからの情報も取得できません。たとえば 内部でデータモジュール select( store ).selector( ... ) を使用することはできません。 これは外部の情報が変更されると、あとで投稿を編集する際にブロックが不正 (invalid) としてマーク付けされる可能性があるためです。詳細には以下の「妥当性検証 (Validation))」を参照してください。 保存の流れで他の情報が必要になった場合、開発者には2つの選択肢があります。

  • ダイナミックブロック を使用してサーバー上で動的に必要な情報を取得する。
  • 外部の値を属性として保存し、変更があった場合にはブロックの edit 関数内で動的に更新する。

ダイナミックブロック の場合、save の戻り値は、ブロックを実装するプラグインが無効化された場合に表示されるブロックコンテンツの、キャッシュしたコピーを返すことができます。

特に指定しない場合、デフォルトの実装ではダイナミックブロックの投稿コンテンツにはマークアップは保存されず、代わりにブロックがサイトのフロントエンド側で表示された際に常に計算するよう延期されます。

attributes

edit 関数と同様 save 関数もまたオブジェクト引数を受け取ります。オブジェクト引数にはマークアップに挿入することができる属性が含まれます。

ESNext

save: ( { attributes } ) => {
    return <div>{ attributes.content }</div>;
}

ES5

save: function( props ) {
    return wp.element.createElement(
        'div',
        null,
        props.attributes.content
    );
}

ブロックを保存する際、属性は、属性ソース定義で指定した形式で保存されます。属性ソースが指定されていない場合、属性はブロックのコメントデリミッターに保存されます。詳細は ブロック属性のドキュメント を参照してください。

属性、editsave を一緒に使用する例をいくつか挙げます。完全に動作するサンプルはブロックのチュートリアルの「属性と編集可能フィールド

子要素への属性の保存

ESNext

attributes: {
    content: {
        type: 'string',
        source: 'html',
        selector: 'p'
    }
},

edit: ( { attributes, setAttributes } ) => {
    const updateFieldValue = ( val ) => {
        setAttributes( { content: val } );
    }
    return <TextControl
            label='My Text Field'
            value={ attributes.content }
            onChange={ updateFieldValue }
        />;
},

save: ( { attributes } ) => {
    return <p> { attributes.content } </p>;
},

ES5

attributes: {
    content: {
        type: 'string',
        source: 'html',
        selector: 'p'
    }
},

edit: function( props ) {
    var updateFieldValue = function( val ) {
        props.setAttributes( { content: val } );
    }
    return wp.element.createElement(
        wp.components.TextControl,
        {
            label: 'My Text Field',
            value: props.attributes.content,
            onChange: updateFieldValue,

        }
    );
},

save: function( props ) {
    return el( 'p', {}, props.attributes.content );
},

シリアライゼーションを通じた属性の保存

理想的には保存する属性はマークアップに含まれるべきですが、常に現実的ではありません。このため属性ソースが指定されない場合、属性はシリアライズされブロックのコメントデリミッターに保存されます。

次の例は「最近の投稿」ブロックのような、マークアップをサーバーサイドでレンダリングするダイナミックブロックになります。save 関数は依然として必要ですが、ブロックはエディターからコンテンツを保存していないため、この例では単純に null を返しています。

ESNext

attributes: {
    postsToShow: {
        type: 'number',
    }
},

edit: ( { attributes, setAttributes } ) => {
    return <TextControl
            label='Number Posts to Show'
            value={ attributes.postsToShow }
            onChange={ ( val ) => {
                setAttributes( { postsToShow: parseInt( val ) } );
            }}
        />;
},

save: () => {
    return null;
}

ES5

attributes: {
    postsToShow: {
        type: 'number',
    }
},

edit: function( props ) {
    return wp.element.createElement(
        wp.components.TextControl,
        {
            label: 'Number Posts to Show',
            value: props.attributes.postsToShow,
            onChange: function( val ) {
                props.setAttributes( { postsToShow: parseInt( val ) } );
            },
        }
    );
},

save: function() {
    return null;
}

妥当性検証 (Validation)

エディターがブロックをロードする際、コンテンツの消失を防止するため投稿コンテンツ内のすべてのブロックは妥当性検証 (validatite) され、その正しさが確かめられます。これはブロックを保存する実装と密接な関係があります。なぜならエディターが正しくブロックをリストアしなければユーザーは意図せずにコンテンツを削除したり、変更するためです。エディターの初期化中、各ブロックのマークアップは、投稿コンテンツからパースされた属性を使用して再生成されます。新しく生成されたマークアップが投稿コンテンツ内の保存済みマークアップと異なる場合、ブロックは不正 (invalid) とマークされます。これはユーザーが編集していない限り、マークアップは保存されたコンテンツと同じはずだと仮定しているためです。

ブロックが不正とマークされると、ユーザーには妥当性検証の失敗をどのように処理するか求められます。

不正なブロックのプロンプト

ブロックのリカバリーを試行 ボタンをクリックすると、できる限りの修復のアクションを試みます。

ブロック側の横の3ドットメニューをクリックすると、3つのオプションが表示されます。

  • 「解決」ボタンをクリックすると「ブロックの問題を解決」ダイアログが開き、2つのオプションを選択できます。
    • HTML に変換: 投稿コンテンツ内の保存済みオリジナルのマークアップを保護し、ブロックをオリジナルのブロックタイプから HTML ブロックタイプに変換します。ユーザーは HTML マークアップを直接変更できます。
    • ブロックへ変換: 投稿コンテンツ内の保存済みオリジナルのマークアップを保護し、ブロックをオリジナルのブロックタイプから検証済みのブロックタイプに変換します。
  • HTML に変換: 投稿コンテンツ内の保存済みオリジナルのマークアップを保護し、ブロックをオリジナルのブロックタイプから HTML ブロックタイプに変換します。ユーザーは HTML マークアップを直接変更できます。
  • クラシックブロックに変換: 投稿コンテンツ内の保存済みオリジナルのマークアップを正しいものとして保護します。ブロックはオリジナルのブロックタイプからクラシックブロックタイプに変換されるため、オリジナルのブロックタイプで利用可能だったコントロールでコンテンツを編集できない可能性があります。

妥当性検証 FAQ

ブロックが不正になるのはどのような場合ですか?

ブロックが不正になる原因には大きく2つあります。

  1. ブロックのコードのフローが、コンテンツの意図しない変更を引き起こした。以下の質問「プラグイン作者です。プラグインが invalid とマークされたらどうやってデバッグすればいいですか ?」を参照してください。
  2. ユーザーまたは外部のエディターがブロックの HTML マークアップを変更して不正となった。

プラグイン作者です。プラグインが invalid とマークされたらどうやってデバッグすればいいですか ?

デバッグを始める前に、上に記述された妥当性検証のステップと、ブロックが不正と検知されるプロセスについて理解してください。ブロックが不正となるのは再生成されたマークアップが投稿コンテンツ内の保存済みマークアップと合致しない場合です。したがって保存されたコンテンツからブロックの属性が正しくパースされなかった場合にしばしば発生します。

属性ソースを使用している場合には、マークアップのソースの属性が期待したとおりに正しいタイプ (通常は 'string' か 'number') で保存されていることを確認してください。

ブロックの不正が検知されるとブラウザーの開発者ツールコンソールに警告が出力されます。警告にはマークアップの相違が発生した正確な場所の詳細が含まれます。期待したマークアップと実際のマークアップの違いを比較し、どこで問題が発生したかを調べてください。

ブロックの save の動きを変えたら古いコンテンツが不正なブロックになりました。どのように修正すればよいですか ?

非推奨ブロック のガイドを参照して、意図したマークアップの変更に古いコンテンツを収容する方法を学習してください。

原文

最終更新日: