お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。
  • まずhistory.push('/path/to/dest')自体のテストを書くことはないはずなので、何かしらの副作用として実行される事が前提です
  • testing-library.com に書いてあることの応用です
/**
 * useHistory() を使えるようにするテストユーティリティ
 */
const renderWithRouter = (children: JSX.Element, { route = '/' } = {}) => {
  window.history.pushState({}, 'Test page', route);

  return render(children, {
    wrapper: BrowserRouter,
  });
};

// spy 用のモック関数
const mockPush = jest.fn();
// react-router-dom をモックにする
jest.mock('react-router-dom', () => ({
  useHistory: () => ({
    // モック関数を設定
    push: mockPush,
  }),
}));

describe('Bar', () => {
  it('history.push のテスト', () => {
    renderWithRouter(<Bar />, {
      route: '/foo/bar',
    });

    // ここらへんに同期的に副作用を起こす処理を書く
    // 以降、副作用で history.push('/foo/baz') されたとする

    // 呼ばれたことの確認
    expect(mockPush).toBeCalled();
    // 引数の確認
    expect(mockPush.mock.calls[0][0]).toBe('/foo/baz');
  });
});
投稿日:
言語::TypeScriptライブラリ::React

サンプルコード

const onSubmit = (ev: React.FormEvent<HTMLFormElement>) => {
  ev.preventDefault();
  const target = ev.target as typeof ev.target & {
    t1: { value: string };
    t2: { value: string };
  };
  console.log(target.t1.value);
  console.log(target.t2.value);
};
return (
  <div className="App">
    <form onSubmit={(ev) => onSubmit(ev)}>
      <input type="text" id="t1" />
      <input type="text" id="t2" />
      <input type="submit" />
    </form>
  </div>
);

参考

ReactでSPAを組んでいてブラウザバックしたときのフォームの入力内容が消し飛んで気になったので、ブラウザバックした時にどうなるのかというのを軽く調査した結果

確認したパターンとしては次の2つ

  1. フォームの入力値をDOMに保持させるステートレスな方式
  2. フォームの入力値をJSに保持させるステートフルな方式

LocalStorageに画面情報を保存して復元するとか、条件分岐を使ってDOMを隠して保持させるとか、そういう系は考慮しない

結果

DOM 保持 JS 保持
SPA 内ブラウザバック 消える 保持される
SPA 外ブラウザバック 保持される 消える
コード記述量 少ない 多い

1. フォームの入力値をDOMに保持させるステートレスな方式

この方式だとhistory.push()でDOMが消し飛ぶのでSPA内でのブラウザバックで入力したデータが保持されません
その代わりSPAの外、別のサイトに遷移してからブラウザバックしたときは、DOMが残っているので入力したデータが保持されます

2. フォームの入力値をJSに保持させるステートフルな方式

この方式だとJSでステート管理をしているため、history.push()で遷移してDOMが消し飛んでも、 defaultValueに持ってる値を突っ込んであげれば、取り敢えず入力したデータを保持することが出来ます
反対にSPAの外、別のサイトに遷移してからブラウザバックしたときは、メモリの中身が飛んでるので入力したデータが保持されません

各手法の実装方式

ちゃちゃっと書いたのでコードは超雑です

確認環境

Env Ver
react 17.0.2
react-dom 17.0.2
react-router-dom 5.2.0

1. フォームの入力値をDOMに保持させるステートレスな方式

大正義ステートレスです
Function Componentはステートレスなので、こうあってほしいですよね~
コードもシンプルで管理しやすいのが素敵なところです

export const StatelessPage = () => {
  const history = useHistory();
  return (
    <div>
      <form>
        <input type="text" />
        <button onClick={() => history.push(AppRoute.dom2.path)}>submit</button>
      </form>
    </div>
  );
};

2. フォームの入力値をJSに保持させるステートフルな方式

ギルティなステートフル方式です
Class Componentを捨てたはずなのにどうしてこうなった…
コードが煩雑で管理が大変です

export const StatefullPage = () => {
  const ctx = useContext(ExampleContext);
  const history = useHistory();
  const onChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
    ctx.text = ev.target.value;
  };

  return (
    <div>
      <form>
        <input
          type="text"
          defaultValue={ctx.text}
          onChange={(ev) => onChange(ev)}
        />
        <button onClick={() => history.push(AppRoute.ctx2.path)}>submit</button>
      </form>
    </div>
  );
};
投稿日:
ライブラリ::Jinja言語::Python

substring

確認環境

Env Ver
Jinja 2.11.0
Python 3.8.5

サンプルコード

  • 変数名[begin:end]で指定する
    • {% set some_variable = string_variable[0:100] %}

ループ処理で変数代入を行う

確認環境

Env Ver
Jinja 2.11.0
Python 3.8.5
  • Jinja2でループ処理の中で変数の足しこみとかをするやつ
    • 代入先の変数宣言は{% set ns = namespace(title = "") %}のようにしてやる必要がある
    • 後はループの中でsetしてやれば上手くいく
    • 変数は宣言したブロックがスコープになるので、スコープを広げたいときは適当にブロックを上げてやると良い

サンプルコード

  • MkDocsのテンプレートでパンくずリストを生成するコード
    • [A] [B] [C]みたいな感じ
{% set ns = namespace(title = "") %}
{% for doc in page.ancestors %}
    {% set ns.title = "[" + doc.title + "] " + ns.title %}
{% endfor %}

テンプレートファイルを読み込む

確認環境

Env Ver
Jinja 2.11.0
Python 3.8.5

サンプルコード

  • フォルダ構成
─src
    ├─app
    │   ├─configs
    │   │    └config.yaml
    │   ├─templates
    │   │         └─main.html
    │   └─__init__.py
    └─main.py
  • コード
from jinja2 import Environment, PackageLoader, select_autoescape
env = Environment(
    # appはフォルダ構成のappフォルダを指す
    loader=PackageLoader('app', 'templates'),
    autoescape=select_autoescape(['html', 'xml'])
)
template = env.get_template('main.html')
# `render()` の引数は埋め込み変数を KeyValue 形式で指定
print(template.render(the='variables', go='here'))

yamlファイルから設定をロードする

確認環境

Env Ver
Jinja 2.11.0
Python 3.8.5

サンプルコード

  • フォルダ構成
─src
    ├─app
    │   ├─configs
    │   │    └config.yaml
    │   ├─templates
    │   │         └─main.html
    │   └─__init__.py
    └─main.py
  • コード
from yaml import safe_load as yamlLoad
from jinja2 import Environment, PackageLoader, select_autoescape
env = Environment(
    loader=PackageLoader('app', 'templates'),
    autoescape=select_autoescape(['html', 'xml'])
)

template = env.get_template('main.html')
with open('app/configs/config.yaml') as fYaml:
    print(template.render(yamlLoad(fYaml)))

概要

  • Jinja2で作られている
    • Python向けのテンプレートエンジン
    • これを理解することで自在にカスタマイズできる。たぶん
  • テンプレート変数について、まともなリファレンスはないので、mkdocsのGitHubリポジトリを眺めて知るのが早い
    • www.mkdocs.org にも一応書いてあるが、網羅されていない

カスタマイズ方法

  • まずはMaterial for MkDocsを読んでリファレンスを理解する
    • 設定で解決する内容ならここで終わり
  • テンプレートのカスタマイズが必要な場合、overrides/main.htmlを生やしてブロック単位でいじる