Help us understand the problem. What is going on with this article?

FluxによるReactアプリの状態管理 ReactHooks編

※ ReducerとVIEWコンポーネントの接続方法に関する記事を追加しました。

ReactHooks学習用にTodoリストを作成した際のまとめです。
Reactの基本事項は既に習得されている方向けです。
ReactのJSX記法を利用する為、Babelなどのトランスパイラを利用します。
webpackなどのモジュールバンドラーの利用も前提としています。
今回は、Parcelを利用しています。

Fluxとは

Reactによるアプリケーション構築を保管する為に考案された、設計パターンです。
データの流れを一方通行にすること、アプリケーションの状態「State」を一元的に管理することで、アプリケーションの管理が煩雑化することを防ぐ試みです。
Fluxによるアプリケーションは、下記のパートで構成されます。

  • Dispatcher: アプリケーションへの更新情報を集約・通知
  • Store: アプリケーションの状態を保持・管理
  • Action: アプリケーションの更新情報を得る為の内部API 
  • View: Reactコンポーネントによるインターフェース

Dispatcherは、アプリケーションに1つだけ存在します。
Storeは、アプリケーションの規模などに応じて1つ以上存在します。
Actionは、アプリケーションに発生しうる変更の数だけ存在します。
Vewは、Storeと接続したコンポーネントを頂点としてツリー構造を形成します。

詳細はこちらの記事で扱っております。
FluxによるReactアプリの状態管理 Flux・FluxUtils編

本家サイトはこちら。
Flux

このFluxを実装する為のフレームワークとして、公式よりFlux Utilsが提供されています。
しかし、React本体に導入されたHooksだけで、Fluxの実装が可能になりました。

Hooksとは

ReactのVer.16.8で新たに導入された機能群です。
Class構文を利用せずに、シンプルな関数の組み合わせでアプリケーションを構築することを可能としてくれます。
2019年9月時点で、10個の組み込みHookが提供されています。
Reactのパッケージに含まれており、共通して名前がuse-で始まる関数です。
その他、自分でカスタムHookを作成することも可能です。

import React { useState, useEffect } from 'react';

Hooks | React

Todoアプリ

ReduxのベーシックチュートリアルにあるTodoサンプルをベースにしてみました。
Hooksを使った構築は、Flux思想を忠実に再現したFluxUtilsを使用して構築した場合と比較すると、Reduxに幾分か寄っている印象です。
その為、Redux・React-Reduxで構築した場合の構造をだいたい維持したまま、Hooksに置き換えてみました。

このRedux・React-ReduxのTodoアプリについてはこちらで扱っております。
FluxによるReactアプリの状態管理 Redux・React-Redux編

この記事で作成するTodoアプリの完全なソースコードはこちらです。
todoapp-flux-hooks
動作のサンプル

Actions

Actionは、Reducerに対して送信するデータです。
実体はJavaScriptのオブジェクトです。
Actionの種類を示す、typeプロパティを必ず持っています。typeプロパティの値は、一般的に文字列が用いられます。
アプリケーションの規模が大きい場合は、予め定数として定義しておくことが望ましいでしょう。
その他Reducerに送信したいデータが含まれます。

新規Todoの追加時のAction
// ActionType
const ADD_TODO = 'ADD_TODO'

// Action
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

このActionを生成するのがActionCreatorです。
Fluxの基本では、Actionの生成から送信までをActionCreator内で実行することになっています。
今回はReduxと同じく、生成して返すのみです。

// Actionの識別子
export const TodoActionTypes = {
  ADD_TODO: 'ADD_TODO',
  /* 略 */
};
// TodoID
let nextTodoId = 0;
/**
 * Todo項目の追加
 * @param {String} text Todoテキスト
 * @returns {Object} Todo追加用action
 */
export const addTodo = (text) => ({
  type: TodoActionTypes.ADD_TODO,
  id: nextTodoId++,
  text
});

アプリケーションのStateの設計

アプリケーションの状態を一つのオブジェクトで表す「State」を設計します。
今回のTodoアプリのStateは、以下のようになります。

{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      id: 1,
      text: 'Write a article about Redux',
      completed: true
    },
    {
      id: 2,
      text: 'Go to the Cats cafee',
      completed: false
    }
  ]
}

visibilityFilterの指定には、予め用意した定数を用います。

actions.js
export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
};

Reducers

Reducerとは

Reducerは、本家FluxではなくReduxで登場するモジュールです。
FluxのStoreの、Stateの変更をする責務を切り出したものです。
これは予めツールなどが用意されているわけではなく、一定のルールに基づいて自分で構築する関数です。
React側では特に指示がないので、Redux側のルールに基づいて作成することとします。

Reducerは、副作用のない純粋な関数です。
以前のStateと新たに生成されたActionを受け取り、新しいStateを返します。

(previousState, action) => newState

previousStateactionの内容が同一であれば、何度呼び出しても同じnewStateを返します。
その為、Reducerには下記のルールがあります。

  • 引数を変更してはいけません。
  • APIの呼び出しやルーティングなどの、副作用が発生する処理を記述してはいけません。
  • 純粋でない関数を呼び出してはいけません。(例:Date.now(), Math.random())
function todoApp(previousState, action){
  switch(action.type){
    case 'TYPE_A':
      return newState;
    case 'TYPE_B':
      return newState;
    default:
      return previousState;
  }
}

ActionTypeでActionの種類を識別し、previousStateactionの情報を基に新しいStateを生成します。
初期呼び出し時は、previousStateundefinedが渡される為、デフォルト引数には、Stateの初期値を設定しています。

Reduxでは、stateの初期値をReducerのデフォルト引数として用意します。
しかし、Hooksを利用する場合は不要になります。

Reducerの分割

アプリケーションの規模が大きい場合、StateおよびReducerのサイズも大きくなってきます。
その場合、一つのReducerで管理することが難しくなる為、適宜分割することが望ましいでしょう。

todosvisibilityFilter、それぞれ管理するReducerを作成します。

reducers/todos.js
const todos = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return // Todo項目追加後newState
    case 'TOGGLE_TODO':
      return // Todo項目切替後newState
    default:
      return state;
  }
};

export default todos;
reducers/visibilityFilter.js
const visibilityFilter = (state, action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter;
    default:
      return state;
  }
};

export default visibilityFilter;

Reducerの結合

関数を分割したものの、最終的に必要なStateは一つのオブジェクトです。
その為、複数のReducerの結果をまとめるReducerが必要です。

js:reducers/index.js
import todos from './todos';
import visibilityFilter from './visibilityFilter';

const rootReducers = (state = {}, action = {}) => {
  return {
    todos: todos(state.todos, action),
    visibilityFilter: visibilityFilter(state.visibilityFilter, action)
  };
};

export default rootReducers;

Reduxでは複数のReducerを束ねる為のcombineReducers()関数が提供されています。
おおよそ下記のようなコードです。

/**
 * Reducerをまとめるヘルパー関数
 * @param {Object} reducers reducerの連想配列
 * @returns {Function} 結合後のReducer関数
 * 
 * 参考: https://github.com/reduxjs/redux/blob/master/src/combineReducers.js
 */
const combineReducer = (reducers) => {
  const reducerKeys = Object.keys(reducers);
  const finalReducers = {};

  for (let key of reducerKeys) {
    const reducer = reducers[key];
    if (typeof reducer === 'function') {
      finalReducers[key] = reducer;
    }
  }

  const finalReducerKeys = Object.keys(finalReducers);

  return (state, action) => {
    let newState = {};
    let hasChanged = false;

    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i];
      const reducer = finalReducers[key];
      const prevState = state[key];
      const nextState = reducer(prevState, action);

      if (typeof nextState === 'undefined') {
        throw new Error(`「 ${key} 」は存在しないStateです。`)
      }
      newState[key] = nextState;
      hasChanged = hasChanged || (nextState !== prevState);
    }

    return hasChanged ? newState : state;
  };
};

// 利用
export default combineReducer({
  todos,
  visibilityFilter
});

useReducerを利用したFluxの構築

Fluxは基本的に、Viewのコンポーネントの外で一元的にアプリケーションの状態を管理する仕組みです。
FluxUtilsではDispatcherとStoreが、ReduxではStoreとReducerというViewから独立したモジュールがこの責務を負っていました。
責務の微妙な違いはあれどStateを保持する「Store」は両方にあります。
Hooksを利用した場合、Stateの保持はView側のルートコンポーネントが担当することになるようです。
実装にはHooksの組み込み関数「useReducer」を利用します。

import React, { useReducer } from 'react';
import Reducer from '../reducers';
import App from '../components/';

export default function(){
  const [state, dispatch] = useReducer(Reducer, initialState);

  return (
    <App state={state} dispatch={dispatch} />
  );
}

useReducer()の第一引数にReducerを渡します。第二引数はStateの初期値です。
戻り値として、Viewで保持するStateと、State更新の為のdispatch関数を受け取ります。
以降、このdispatch関数の引数にActionを渡すと、Reducerが実行され、新たなstateがViewに渡されるようになります。

FluxUtilsやReact-Reduxでは、ViewにStateを引き渡す前に以前のStateと新しいStateの比較を行っていました。
useReducerを利用した場合も同様の比較確認が行われ、変更を検知できた場合に再レンダリングが発生します。

useReducer | React

ContextとuseContext

ルートコンポーネントで保持されているStateは、基本気にpropsを通じて下位コンポーネントへと共有されます。
その階層がふかくなると、長いバケツリレーが発生してしまいます。
Ver.16.3で導入された「Context」を利用すると、下位コンポーネントからも等しくStateにアクセスできるようになります。

Context | React

利用準備

Contextオブジェクトを生成して公開します。
アプリケーション内で複数作成することが可能です。

myContext.js
import React from 'react';

const MyContext = React.createContext(defaultValue);
export default MyContext;

// 初期値defaultValueは省略可

React.createContext | React

データの提供

Contextオブジェクトに付属するProviderコンポーネントを利用して、提供したいデータを登録します。

import MyContext from './myContext';
import RootApp from './rootApp';

function App(){
  // valueプロパティにに渡す
  return (
    <MyContext.Provider value={AppStateData}>
      <RootApp />
    </MyContext.Provider>
  );
}

Context.Provider | React

データの読み取り

Providerを通して提供されたデータを、下位のコンポーネントが直接受け取るには、2種類の方法があります。

Consumerコンポーネント

Contextオブジェクトに付属するConsumerコンポーネントを利用して、Providerで提供されたデータにアクセスします。
Consumerコンポーネントにコールバック関数を渡すと、引数でProviderのvalueプロパティに渡されたデータを受け取れます。

import MyContext from './myContext';

function SomeContainer(){
  return (
    <MyContext.Consumer>
      {({ param, callBack }) => {
        <button onClick={callBack}>
          {param}
        </button>
      }}
    </MyContext.Consumer>
  );
}

Context.Consumer | React

useContext

useContextを利用すると、Consumerコンポーネントよりもすっきりと記述できます。
useContextの引数にContextオブジェクトを渡すと、戻り値としてStateを受け取れます。

import React, { useContext } from 'react';
import MyContext from './myContext';

function SomeContainer(){
  const { param, callBack } = useContext(MyContext);
  return (
    <button onClick={callBack}>
      {param}
    </button>
  );
}

useContext | React

PresentationalComponents と ContainerComponents

ReactとReduxでアプリケーションを構築する際は、基本的にコンポーネントをPresentationalComponentとContainerComponentの2種に分けます。
PresentationalComponentはビジネスロジックを持たず、描画に徹します。
ContainerComponentはReduxと接点を持ち、PresentationalComponentに必要なpropsを受け渡します。
こうすることで、より見通しの良いアプリケーションの構築を可能とし、コンポーネントの再利用性を高めます。

Reducerの時と同様、View側もReduxのルールを取り入れて構築することとします。

Presentational and Container Components | Redux

ルートコンポーネントの場合

ルートコンポーネントでは、useReducerを用いてReducerと接続しつつ、Contextで下位コンポーネントへデータを提供する仕組みを準備します。

import React, { useReducer } from 'react';
import TodoContext from '../context';
import TodoReducer from '../reducers';
import TodoApp from '../components/';
import { VisibilityFilters } from '../actions';

const initialState = {
  visibilityFilter: VisibilityFilters.SHOW_ALL,
  todos: []
};

const App = () => {

  const [state, dispatch] = useReducer(TodoReducer, initialState);
  const value = { ...state, dispatch };

  return (
    <TodoContext.Provider value={value}>
      <TodoApp />
    </TodoContext.Provider>
  );
};

export default App;

useReducerを実行して得られたstatedispatchを、Providerコンポーネントへ渡します。
その際、stateとdispatchをまとめたオブジェクトを作成してvalueプロパティに渡しています。

Contextを利用するに当たり、この毎回オブジェクトを作成して渡すというのはパフォーマンス上、あまりよろしくないそうです。
Providerを含むコンポーネントが再レンダリングされる度に、Providerに渡しているオブジェクトが新しくなってしまうため、valueに渡しているStateに変更が無くとも関連するConsumerを含むコンポーネントの再レンダリングが発生してしまいます。
上記の例では、Stateの更新以外に再レンダリングが発生する要因が無いので、問題ないかなと思っています。

コンテクスト注意事項 | React

TodoListの場合

Todoリストを表示するコンポーネントのツリーは下記のとおりです。
VisibleTodoListがContainerComponentsです。

Provider
  └ VisibleTodoList ☆
      └ TodoList
          └ Todo

useContextを使ってStateから必要な項目を取得します。
todoリストの配列は現在のフィルターにマッチするものを抽出して、下位コンポーネントに渡します。
また、項目をクリック時に完了状況を切り替えるためのコールバック関数も定義しています。

import React, { useContext } from 'react';
import TodoContext from '../context';
import TodoList from '../components/todoList';
import {
  toggleTodo,
  VisibilityFilters
} from '../actions';


const VisibleTodoList = () => {

  const { todos, visibilityFilter, dispatch } = useContext(TodoContext);
  let visibleTodos;

  switch (visibilityFilter) {
    case VisibilityFilters.SHOW_ALL:
      visibleTodos = todos;
      break;
    case VisibilityFilters.SHOW_COMPLETED:
      visibleTodos = todos.filter(t => t.completed);
      break;
    case VisibilityFilters.SHOW_ACTIVE:
      visibleTodos = todos.filter(t => !t.completed);
      break;
    default:
      throw new Error('Unknown filter ' + visibilityFilter);
  }

  return (
    <TodoList
      todos={visibleTodos}
      toggleTodo={id => { dispatch(toggleTodo(id)) }}
    />
  );
};

export default VisibleTodoList;

AddTodoの場合

Todoに項目を追加するコンポーネントを構築します。
Form要素を含むコンポーネントなど、責務の分離が難しい場合は、分離しないという選択肢もあります。

import React, { useState, useContext } from 'react';
import TodoContext from '../context';
import { addTodo } from '../actions';

const AddTodo = () => {

  const [inputText, setInputText] = useState('');
  const { dispatch } = useContext(TodoContext);

  return (
    <div>
      <form
        onSubmit={e => {
          e.preventDefault();
          if (!inputText.trim()) {
            return;
          }
          dispatch(addTodo(inputText));
          setInputText('');
        }}
      >
        <input
          value={inputText}
          onChange={e => setInputText(e.target.value)}
        />
        <button type="submit">Add Todo</button>
      </form>
    </div>
  );
};

export default AddTodo;

useContext

関数コンポーネント内で、ローカルなStateを持つ為のHookです。
inputフォームの値を保持する為に利用しています。

const [state, setState] = useState(initialState);

useState | React

イメージ図

自分なりにA4にまとめてみました。
TodoList_Hooks.PNG

2019年6月30日追記

Reduxのからの流れで当たり前の様にReducerを一つにまとめていたのですが、ReactのReducerは必ずしも一つにまとめる必要はなさそうです。
下記の記事を参考にいたしました。
How to use useReducer in React Hooks for performance optimization

useReducerを複数利用する

わざわざcombineReducerでまとめなくても、FluxのStoreがそうであるように、ReactのReducerも管理単位ごとに複数存在させます。
それに応じて接点を提供するuseReducerも複数利用します。

import React, { useReducer } from 'react';
import TodoContext from '../context';
import {
  TodosReducer,
  FilterReducer
} from '../reducers';
import TodoApp from '../components/';
import { VisibilityFilters } from '../actions';

const App = () => {

  const [todos, dispatchTodo] = useReducer(TodosReducer, []);
  const [visibilityFilter, dispatchFilter] = useReducer(FilterReducer, VisibilityFilters.SHOW_ALL);

  const value = {
    todos,
    visibilityFilter,
    dispatchTodo,
    dispatchFilter
  };

  return (
    <TodoContext.Provider value={value}>
      <TodoApp />
    </TodoContext.Provider>
  );
};

export default App;

本記事はあくまでFluxの記事なのでuseReducerによるReducerとの接点をVIEWのルートコンポーネントにしか置いていませんが、useReducerおよびReducerは、個々のコンポーネントのローカルStateの管理に用いることも、もちろん可能です。

// 一つの関数コンポーネント内でstateの管理が煩雑化した場合に、外部化させる
const localReducer = (state, action) => {
  switch (action.type) {
    case 'hoge':
      return {
        ...state,
        hoge: action.value
      };
    // ...
  }
}

const SomeComponent = (props) => {
  const [state, dispatch] = useReducer(localReducer, initialState);

  const onClick = (value) => {
    dispatch({
      type: 'hoge',
      value
    });
  };
  // ...
}

useYourImagination() | React

Reducer毎にContextを用意する

ContextもReducer毎に用意して、もうちょっとルートコンポーネントにおける接続部分をすっきりさせてみます。

Hooksを利用した新Store

TodosのReducerとContextを、useReducerを使ってひとまとめにします。

import React, { useReducer, useContext } from 'react';
import { TodoActionTypes } from '../actions';

const Context = React.createContext();

// Reducer
const reducer = (state, action) => {
  switch (action.type) {
    case TodoActionTypes.ADD_TODO:
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ];
    case TodoActionTypes.TOGGLE_TODO:
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    default:
      return state;
  }
};

// Todos専用Provider
const TodosProvider = ({ children }) => {
  const contextValue = useReducer(reducer, []);

  return (
    <Context.Provider value={contextValue}>
      {children}
    </Context.Provider>
  );
};

// TodosのstateとDispatcherを提供するカスタムHook
const useTodos = () => {
  const contextValue = useContext(Context);
  return contextValue;
}

export { TodosProvider, useTodos };

useTodosはカスタムHookと呼ばれる、ユーザが独自に定義できるHookです。
内部でビルトインHooksを利用しつつも、Reactコンポーネントは返しません。
VisibilityFilterも同様にひとまとめにします。
ルートコンポーネントでは、各Storeから提供されているProviderを設置するだけです。
ロジックが無くなってすっきりしたので、VIEWをまとめる為だけの中間コンポーネントは廃止しました。

import React from 'react';
import {
  TodosProvider,
  FilterProvider
} from '../stores';
import AddTodo from './addTodo';
import VisibleTodoList from './visibleTodoList';
import Footer from '../components/footer';


const App = () => {

  return (
    <FilterProvider>
      <TodosProvider>
        <AddTodo />
        <VisibleTodoList />
        <Footer />
      </TodosProvider>
    </FilterProvider>
  );
};

export default App;

下位のContainerコンポーネントでは、各Storeから提供されているカスタムHookを利用します。
カスタムHookもビルトインHookと同様、コンポーネントのトップレベルのスコープで実行する必要があります。

import React from 'react';
import {
  useTodos,
  useFilter
} from '../stores';
import {
  VisibilityFilters,
  toggleTodo
} from '../actions';
import TodoList from '../components/todoList';

const VisibleTodoList = () => {

  // useReducer利用時と同じ要領で分割代入
  const [todos, dispatch] = useTodos();
  const [visibilityFilter] = useFilter();

  let visibleTodos;

  switch (visibilityFilter) {
    case VisibilityFilters.SHOW_ALL:
      visibleTodos = todos;
      break;
    case VisibilityFilters.SHOW_COMPLETED:
      visibleTodos = todos.filter(t => t.completed);
      break;
    case VisibilityFilters.SHOW_ACTIVE:
      visibleTodos = todos.filter(t => !t.completed);
      break;
    default:
      throw new Error('Unknown filter ' + visibilityFilter);
  }

  return (
    <TodoList
      todos={visibleTodos}
      toggleTodo={id => { dispatch(toggleTodo(id)) }}
    />
  );
};

export default VisibleTodoList;

Providerコンポーネントは、必ずしもルートに全部まとめる必要は無いかと思います。
ここまでのコードはこちらにあります。
multiple_store | github

フックのルール
カスタムフックの作成 | React

ReduxとContextの接続ツール

todosStorevisibilityFilterStoreで同じ事をやっているので、まとめられるのでは?と思ってやってみました。

connect.jsx
import React, { useReducer, useContext } from 'react';

/**
 * ReducerとContextを接続する
 * @param {Function} reducer 対象Reducer
 * @param {*} initialState state初期値
 */
const connect = (reducer, initialState) => {

  const Context = React.createContext();

  const Provider = ({ children }) => {
    const contextValue = useReducer(reducer, initialState);

    return (
      <Context.Provider value={contextValue}>
        {children}
      </Context.Provider>
    );
  };
  Provider.displayName = reducer.name && (reducer.name !== 'anonymous') ? `Provider(${reducer.name})` : 'Provider';

  const useConsumer = () => {
    const contextValue = useContext(Context);
    return contextValue;
  };

  return { Provider, useConsumer };
};

export default connect;

ここで登場するdisplayNameは、JavaScriptの非推奨プロパティのdisplayNameではなく、Reactコンポーネントのプロパティです。
デバッグツール利用時に表示される名前を指定するのに利用します。

displayName | React

Reducerとの接続

import connect from './connect';

// Reducer
const todos = (state, action) => {
  switch (action.type) {
    case TodoActionTypes.ADD_TODO:
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ];
    case TodoActionTypes.TOGGLE_TODO:
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    default:
      return state;
  }
};

// Reducerと初期値を渡すのみ
export default connect(todos, []);

Providerの利用

import TodosStore from '../stores/todoStore';
import FilterStore from '../stores/visibilityFilterStore';

const App = () => {

  return (
    <FilterStore.Provider>
      <TodosStore.Provider>
        <AddTodo />
        <VisibleTodoList />
        <Footer />
      </TodosStore.Provider>
    </FilterStore.Provider>
  );
};

Consumerの利用

React.useStateという記述方法に倣ってみました。
ただ、eslint-plugin-react-hooksではHooksと認識されませんでした。
一応これで無事動いてはいるようです。ご参考までに……。

import TodosStore from '../stores/todoStore';
import FilterStore from '../stores/visibilityFilterStore';

const VisibleTodoList = () => {

  const [todos, dispatch] = TodosStore.useConsumer();
  const [visibilityFilter] = FilterStore.useConsumer();

  let visibleTodos;

  switch (visibilityFilter) {
    case VisibilityFilters.SHOW_ALL:
      visibleTodos = todos;
      break;
    case VisibilityFilters.SHOW_COMPLETED:
      visibleTodos = todos.filter(t => t.completed);
      break;
    case VisibilityFilters.SHOW_ACTIVE:
      visibleTodos = todos.filter(t => !t.completed);
      break;
    default:
      throw new Error('Unknown filter ' + visibilityFilter);
  }

  return (
    <TodoList
      todos={visibleTodos}
      toggleTodo={id => { dispatch(toggleTodo(id)) }}
    />
  );
};

参考情報

React HooksとContextAPIでFluxをやってみる
Redux, Flux, and the React Hooks API, Which should I use?

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away