20201012のReactに関する記事は5件です。

[Redux Toolkit] 4つの主要APIについて

はじめに

Redux、、、難しい。。。
React/Reduxの学習を始めたフロントエンド初心者の方なら誰しも少なからず感じるはず。。。

Redux Toolkitの導入により開発が大分楽になってくるのは分かるが、それでも、やっぱり初学者にとって、役割ごとに書くコードが多い気がする。ただ、確かに、Redux Toolkitを使って開発することによって、Reduxの定型文がすっきり書けることも、書き比べることによって少しずつ実感できてきた。

...という事で、個人的な記憶の定着の意味合いが強いのですが、Redux Toolkitを使ってれば必ず使用するであろう、主要なメソッドをまとめました。

詳しくは公式ドキュメントを。

そもそもRedux Toolkitって?

Reduxの開発チームが効率的で快適なDXを目指して開発したライブラリです。
Reduxは、最小限の機能しか持っていないため、導入するためには、役割ごとに各モジュールを各自で準備しないといけません。

それは、たとえ、Reduxに習熟したとしても、必要になる定型文のようなコードをいちいち書かなければいけない事を意味します。また、アプリの規模が大きくなってくると、様々な種類のデータをどのように持つか、Storeの分割をどのようにするかということも、各自、各チームで判断しないといけなくなります。(Ducksやre-ducks等のいくつかのパターンが提案されているのみ)

上記の課題を解決する上で、Redux Toolkitを導入することは、生産性の向上に繋がるとして、注目されているように感じます。では、主要なAPIの構文を見ていきましょう。

configureStore

通常、ReduxStoreを作成するには、createStore()を呼び出してReducer関数を渡していましたが、Redux Toolkitでは、configureStore()を使用します。

createStoreとの違いは、複数の引数を渡すのでなく、名前付きフィールドを持つ単一のオブジェクトを渡す点です。reducerという名前のフィールドとして渡す必要がある点に注意が必要です。

//Redux
const store = createStore(counter)

//Redux Toolkit
const store = configureStore({
  reducer: counter
})

createAction

createActionは引数にActionTypeの文字列を受け取り、その文字列のAction Creator関数を返します。既存の方法でAction Creatorを作るよりも、短くシンプルに書くことができます。

function createAction(type, prepareAction?)
ReduxToolkitを導入していない場合のActionCreator作成の記法
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";

function incremnetOriginal() {
  return { type: INCREMENT };
}

function decrementOriginal() {
  return { type: DECREMENT };
}

console.log(incremnetOriginal(), decrementOriginal());
// { type: 'INCREMENT' } { type: 'DECREMENT' }
ReduxToolkitのcreateActionを使用
// createAction関数を使うことによって上記と同じ処理を短くかけます。
const incrementNew = createAction<number | undefined>('INCREMENT');
const decrementNew = createAction<number | undefined>('DECREMENT');

console.log(incremnetNew(), decrementNew());
// { type: 'INCREMENT' } { type: 'DECREMENT' }

ReducerでActionTypeの文字列を参照する場合は、以下2パターンの方法で書くことができます。

const incremnet = createAction<number | undefined>('INCREMENT');

// ①
console.log(incremnet.toString()); // "INCREMENT"
// ②
console.log(increment.type); // "INCREMENT"

Action Creatorは引数無しで呼び出すことも、ActionにアタッチするPayloadを指定して呼び出すことも出来ます。

let action = increment();
// { type: 'INCREMENT' }

action = increment(10);
// { type: 'INCREMENT', payload: 10 }

prepare callbackについて

デフォルトでは、生成されたAction Creatorは単一の引数を受け入れ、それが、action.payloadになります。しかし、Action Creatorの複数のパラメータを受け入れたり、ランダムなIDを生成したり、タイムスタンプを取得したりするなど、payloadの生成をカスタマイズするために、追加のロジックを書きたい場面があったりします。これを実現するためには、createAction関数の第二引数に、payload値を構築するために使用される、prepare callbackを指定することができます。

prepare callbackが指定された場合、Action Creatorの全ての引数は、prepare callbackに渡され、payloadフィールドを持つオブジェクトを返します。

import { createAction, nanoid } from '@reduxjs/toolkit';

const addTodo = createAction('todos/add', function prepare(text: string){
  return {
    payload: {
      text,
      id: nanoid(),
      createdAt: new Date.toISOString(),
    }
  }
});

console.log(addTodo('Study for App'))
/**
*{
*  type: 'todos/add',
*  payload: {
*    text: 'Study for App',
*    id: '4JIureoiO104Yijd',
*    createdAt: 2020-10-11T07:53:36.581Z'
*  }
*}
**/

matchメソッドについて

生成されたAction Creatorは.match(action)メソッドを持っています。

これは、渡されたActionがActionCreatorによって作成されたActionと同じTypeかどうかを判定するために使用することが可能です。

import { createAction, Action } from '@reduxjs/toolkit'

const increment = createAction<number>('INCREMENT')

function someFunction(action: Action) {

  if (increment.match(action)) {
    {/* 何らかの処理 */}
  }
}

createReducer

通常、ReduxのReducerは、action.typeフィールドをチェックして、ActionTypeごとに特定のロジックを実行していました。

Switch文を使用する既存の方法
function counter(state = 0, action) {
  switch (action.type) {
    case increment.type:
      return state + 1
    case decrement.type:
      return state - 1
    default:
      return state
  }
}

Redux Toolkitでは、createReducer関数を使用して以下のように書くことができます。ActionTypeの文字列をKeyとして使用する必要があるので、ES6の「computed property」構文を使用して、ActionType文字列からKeyを作成することが出来ます。

createReducerを使用してシンプルに書く
const increment = createAction('INCREMENT');
const decrement = createAction('DECREMENT');

const counter = createReducer(0, {
  [increment.type]: state => state + 1,
  [decrement.type]: state => state - 1
})

以下のような書き方も可能です。
computed propertiesは、内部にあるどのような変数に対してもtoString()を呼び出すので.typeフィールドを使用せずに、ActionCreator関数を直接使用することも可能です。

const counter = createReducer(0, {
  [increment]: state => state + 1,
  [decrement]: state => state - 1
})

createSlice

上記でAction Creatorを個別に作成していますが、createSliceを使用する事によって、State,Reducer,Action Creatorはまとめて作成することが可能です。

createSliceは、reducerとactionsを格納したsliceオブジェクトを返します。
createSliceは、以下のオプションを持つオブジェクトパラメータを受け取ります。

function createSlice({
    // ActionTypeでプレフィックスとして使用される名前
    name: string,
    // Reducerで使用される初期値
    initialState: any,
    // reducersオブジェクト。Key名からActionを生成します。
    reducers: Object<string, ReducerFunction | ReducerAndPrepareObject>
    // Reducerを追加するために使用されるコールバックまたは、reducersの追加オブジェクト。
    extraReducers?:
    | Object<string, ReducerFunction>
    | ((builder: ActionReducerMapBuilder<State>) => void)
})

reducersフィールドは、caseReducer関数(特定のActionTypeを扱うことを目的とした関数で、Switch構文内のcase文に相当)を含むオブジェクトです。KeyはActionTypeを生成するために使用されます。

const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: state => state + 1,
    decrement: state => state - 1
  }
})

const store = configureStore({
  reducer: counterSlice.reducer
})

document.getElementById('increment').addEventListener('click', () => {
  store.dispatch(counterSlice.actions.increment())
})

大体は、Destructuring assignmentを使用して、以下のように、Action CreatorやReducerを取り出すことになるかと思います。

const { actions, reducer } = counterSlice;
const { increment, decrement } = actions;

ActionCreatorのpayloadをカスタマイズする必要がある場合、prepareフィールドにコールバック関数を定義します。この場合、reducerprepare、二つのプロパティを含めなければいけません。

import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import nanoid from 'nanoid'

interface Item {
  id: string
  text: string
}

const todosSlice = createSlice({
  name: 'todos',
  initialState: [] as Item[],
  reducers: {
    addTodo: {
      reducer: (state, action: PayloadAction<Item>) => {
        state.push(action.payload)
      },
      prepare: (text: string) => {
        const id = nanoid()
        return { payload: { id, text } }
      },
    },
  },
})

さいごに

公式が推奨する開発アプローチをまとめたRedux Style Guideでも推奨されており、実際に書いていても、記述量も減り、導入するメリットは大きいのではないかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

styled-componentsの概要

 styled-componentsの使い方と役立つと思った箇所をまとめてみました。

メリット

  • コンポーネントのスタイル定義が1つのファイルの中で完結すること
  • スタイリングそのものの記述はCSSと同じであること

使い方

npm install --save styled-componentsyarn add styled-componentsでインストール

import styled from 'styled-components';

const Example: React.FC = () => {
  return <Wrapper>こんにちは</Wrapper>;
};

const Wrapper = styled.div`
  // CSSの指定
  font-size: 50px;
`;

便利な機能

コンポーネントを拡張することができる

  • 全体で統一しているスタイルに対して細部の調整を加えたいときに便利である。

    例) Wrapperコンポーネントを継承してNewWrapperコンポーネントを作るとき
const Wrapper = styled.div`
  font-weight: 200;
`;

const NewWrapper = Wrapper.extend`
  color: red;
`;

styled-componentsにpropsを渡すこともできる。

  • propsを渡すことで作成したコンポーネントの汎用性をより高めることが可能に。

    例) 上の「こんにちは」のfont-sizeを10px、下の「こんにちは」のfont-sizeを100pxにしたいとき
type ExampleProps = {
  size: boolean;
};

const Example: React.FC<ExampleProps> = ({size}) => {
  return (
    <div>
      <Wrapper size=true >こんにちは</Wrapper>
      <Wrapper size=false >こんにちは</Wrapper>
    </div>
 );
};

const Wrapper = styled.div<{size: boolean}>`
  font-size: 50px;
  color: red;
  font-weight:${({ size }) => size ? '10' : '100'}px;
`;

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[React] importする際に絶対パスで書きたい

Reactでコンポーネントをimportする時などに、いちいち相対パスで書くのは面倒。そんな時は、絶対パスで書けるように設定しておきましょう。

//こうじゃなくて。。。
import Button from '../../components/atom/Button'

//こう書きたい
import Button from 'components/atom/Button'

設定

簡単です。
Create React Appのドキュメントに書いてあります。

プロジェクトのルートに、 jsconfig.json 又は、 tsconfig.jsonを作成して以下を追記するのみです。

jsconfig.json又はtsconfig.json

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

これでいちいち相対パスで書かなくて済みます。

Create React App [Absolute Imports]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-selectで全選択とか独自UIの実装 & filterOptionのコントロール

react-selectで全選択の実装

react-selectで独自のボタンと埋め込む時の備忘録

こんなのをしたい

全選択とか全解除で複数選択を楽させたい

スクリーンショット 2020-10-12 18.38.19.png

成果物

codesandbox

ソース
import React, { useState } from "react";
import "./styles.css";
import ReactSelect, { components, createFilter } from "react-select";

const options = [
  { value: "ocean1", label: "Ocean", color: "#00B8D9" },
  { value: "blue", label: "Blue", color: "#0052CC" },
  { value: "purple", label: "Purple", color: "#5243AA" },
  { value: "red", label: "Red", color: "#FF5630" },
  { value: "orange", label: "Orange", color: "#FF8B00" },
  { value: "yellow", label: "Yellow", color: "#FFC400" },
  { value: "green", label: "Green", color: "#36B37E" },
  { value: "forest", label: "Forest", color: "#00875A" },
  { value: "slate", label: "Slate", color: "#253858" },
  { value: "silver", label: "Silver", color: "#666666" }
];

const widget = {
  label: "Hoge",
  value: "*"
};

export default function App() {
  const [selected, setSelected] = useState([]);

  const Option = (props) => {
    //  値が*の時は独自のボタンとかを描画
    if (props.data.value === "*") {
      return (
        <div style={{ display: "flex", justifyContent: "space-around" }}>
          <button
            onClick={() => {
              setSelected(options);
            }}
          >
            全選択
          </button>
          <button
            onClick={() => {
              setSelected([]);
            }}
          >
            全解除
          </button>
        </div>
      );
    }

    return <components.Option {...props} />;
  };

  // 独自UIはfIlterの対象外に
  const withOutWidgetFilter = (candidate, input) => {
    if (input && candidate.value === "*") {
      return true;
    }

    return createFilter(null)(candidate, input);
  };

  return (
    <div className="App">
      <h1>React Select With Widgets Example</h1>

      <div style={{ width: "500px", margin: "auto" }}>
        <ReactSelect
          value={selected}
          options={[widget, ...options]}
          components={{ Option }}
          isMulti
          closeMenuOnSelect={false}
          hideSelectedOptions={false}
          filterOption={withOutWidgetFilter}
          onChange={(value) => {
            setSelected(value);
          }}
        />
      </div>
    </div>
  );
}

解説

肝としては、optionsを拡張して独自の機能を埋め込んでいる

  const Option = (props) => {
    // 値が*の時は独自のボタンとかを描画
    if (props.data.value === "*") {
      return (
        <div style={{ display: "flex", justifyContent: "space-around" }}>
          <button
            onClick={() => {
              setSelected(options);
            }}
          >
            全選択
          </button>
          <button
            onClick={() => {
              setSelected([]);
            }}
          >
            全解除
          </button>
        </div>
      );
    }

    return <components.Option {...props} />;
  };

react-selectの絞り込み機能で埋め込んだUIを絞り込みの対象にしたないときはこうする

  const withOutWidgetFilter = (candidate, input) => {
    // 独自UI用の値の時はtrueを返しfilteringの対象外に
    if (input && candidate.value === "*") {
      return true;
    }

    // デフォルトの絞り込み処理
    return createFilter(null)(candidate, input);
  };

ざっくりとこんな感じで実現可能です
絞り込んだ値に対しての全選択とかはもう少し処理が必要ですが、ベースはこんな感じです

おまけ

Filterで絞り込まれている値ははSelectのrefから参照できます

const ref = React.useRef(null);

// 絞り込まれた値が取得できる
const filtedOptions = ref.current?.select.state.menuOptions.render || []

return <Select
        ref={ref}
        {...props}
       />
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsでURLパラメータを取得する

getServerSidePropsを使う

http://localhost:3000/hoge?slug=tokyo
このslug部分を取得したい。

export async function getStaticProps({query}) {
  console.log(query.slug)
  return {}
}
// undefined

export async function getServerSideProps({query}) {
  console.log(query.slug)
  return {}
}
// tokyo

詳細はこちらに

https://nextjs.org/docs/basic-features/data-fetching

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む