- 投稿日:2020-10-12T21:54:29+09:00
[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フィールドにコールバック関数を定義します。この場合、
reducer
とprepare
、二つのプロパティを含めなければいけません。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でも推奨されており、実際に書いていても、記述量も減り、導入するメリットは大きいのではないかと思います。
- 投稿日:2020-10-12T21:30:56+09:00
styled-componentsの概要
styled-componentsの使い方と役立つと思った箇所をまとめてみました。
メリット
- コンポーネントのスタイル定義が1つのファイルの中で完結すること
- スタイリングそのものの記述はCSSと同じであること
使い方
npm install --save styled-components
かyarn 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; `;参考
- 投稿日:2020-10-12T20:48:31+09:00
[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"] }これでいちいち相対パスで書かなくて済みます。
- 投稿日:2020-10-12T18:49:50+09:00
react-selectで全選択とか独自UIの実装 & filterOptionのコントロール
react-selectで全選択の実装
react-selectで独自のボタンと埋め込む時の備忘録
こんなのをしたい
全選択とか全解除で複数選択を楽させたい
成果物
ソース
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} />
- 投稿日:2020-10-12T00:54:35+09:00
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詳細はこちらに