20200214のReactに関する記事は7件です。

React Native Modal Tutorial with Examples

A comprehensive step by step tutorial on how to work with React Native Modal component. In this tutorial, we will learn how to show essential content such as title, text and image with Modal popup in React Native application.

click here to read more
https://www.positronx.io/react-native-modal-tutorial-with-examples/

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

React Native Image Component Tutorial with Examples

In this article, we are going to have an idea about how to show various image types in React Native application using Image Component.

click here to read more
https://www.positronx.io/react-native-image-component-tutorial-with-examples/

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

React Native Table Component Tutorial with Example

how to build a Table component in React Native application and display data in the table. We will create a responsive table in React Native application, that table will be horizontally and vertically scrollable

click here to read more
https://www.positronx.io/react-native-table-component-tutorial-with-example/

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

Build React Native Custom Checkbox Component for Android and iOS

In this tutorial, we are going to learn how to create a custom Checkbox component in React Native application for Android and iOS platforms. We will learn step by step how to get Multiple checkboxes values on button click using React Native APIs.

READ MORE TO CLICKHERE

https://www.positronx.io/build-react-native-custom-checkbox-for-android-and-ios/

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

【ハンズオン付き】unstated-nextでライトにReactの状態管理をしてみる

目次

  • unstated-nextとは
  • unstated-nextの特徴
  • どんな人・どんなケースにオススメか
  • ハンズオン
  • Tips
    • 複数のContainerを定義する
    • Containerが増えたときネストしたくない
    • 非同期処理
  • 参考文献

対象読者

・redux触ってみて疲弊したReact初心者
・ReactHooks大好きclass絶対書きたくないマン

React始めたばかりでHooksを触ったことがない人でもとりあえず動かせるようにハンズオンも容易しています。

unstated-nextとは

React HooksベースはReactの状態管理ライブラリです。

個人的な複雑な状態管理を必要としない小規模のツール系のアプリならばReduxではなくこのunstated-nextをファーストチョイスとして良いと思うほど気に入っているライブラリです。

unstated-next
Image from Gyazo

unstated-nextの特徴

Pros

・ソースコードが非常に小さいので全容の把握が容易(全38行)
・従って学習コストが非常に低い
・書かなければいけないコードの行数が少ない
・React Hooksの非常に薄いラッパーなのでカスタマイズしやすい

Cons

・複雑な状態管理, 大規模アプリに向いているかは(筆者には)わからない。
・React Hooksを学ぶための学習コストは必要。Class構文に慣れている人は少し疲れるかも。

どんな人にオススメか

何を言っても状態管理を全て純粋なReactHooksで書けるのが大きいです。関数型が好きで classなんて書きたくない! という人は気に入ると思います。

また学習コストの低さ、記述のシンプルさ
複雑な状態管理が必要ないアプリ、小規模なアプリの開発に使うのであれば間違いなくファーストチョイスとして良いライブラリだと思います。Reduxはreducerを創るためにキーボードを叩く指が疲れてきますからね...

使い方

簡単な例としてUserに関するグローバルアクセス可能な状態をunstated-nextを使ってつくってみましょう。

ハンズオン

このハンズオンではReactの環境構築から行っていきます。

最終的なディレクトリ構造は以下のようになります

- dist
    - index.html
- src
    - containers
        - compose.tsx
        - GeneralContainer.ts
        - UserContainer.ts
        - PlanContainer.ts
    - main.tsx
- package-lock.json
- package.json
- tsconfig.json
- webpack.config.js

0. npmでReact/TypeScriptの開発に必要なライブラリを入れていきます。

mkdir unstated-next-sample
cd unstated-next-sample
npm init -y
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader
npm i -S react react-dom @types/react @types/react-dom

1. tsconfig.json と webpack.config.js と定義します

tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5",
    "module": "es2015",
    "jsx": "react",
    "moduleResolution": "node",
    "lib": [
      "es2020",
      "dom"
    ]
  }
}
webpack.config.js
module.exports = {
  mode: "development",

  entry: "./src/main.tsx",
  output: {
    path: `${__dirname}/dist`,
    filename: "main.js"
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader"
      }
    ]
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  devServer: {
    contentBase: `${__dirname}/dist`
  }
};

2. 簡単なReactComponentを定義しましょう。

src/main.tsxに簡単なReactComponentを定義しましょう。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

const App: React.FC = (props) => {
  return (
    <div>
      <h1>UnstatedNext!</h1>
    </div>
  )
}

+const app = document.getElementById("app")
+ReactDOM.render(<App />, app)

dist/index.htmlも作成します。

dist/index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>UnstatedNext</title>
</head>

<body>
  <div id="app"></div>
  <script src="main.js"></script>
</body>

</html>

3. webpack-dev-serverを起動しましょう。

package.jsonのscriptの部分に以下の記述を追加します。

package.json
{
  "scripts": {
    "start": "webpack-dev-server",
  },
}

ターミナルから npm start と入力すると開発サーバーが立ち上がるはずです。

4. unstated-nextをインストールします。

npm install -S unstated-next

5. 状態を保持するためのContainerを定義します。

試しにUser情報を保持・更新するためのUserContainerを定義してみましょう。ファイルの場所は src/containers/UserContainer.ts とします。

src/containers/UserContainer.ts
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type User = {
  name: string
}
const initialUser: User = { name: "" };

const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  return { user, setUser }
};

const UserContainer = createContainer(useUser);

export { UserContainer }

なんとunstated-nextはこれだけで状態管理が可能になります。

6. Containerを呼び出してみよう

ではUserContainerの状態にアクセスするために、少しmain.tsxを編集していきます。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { UserContainer } from "./containers/UserContainer"

const App: React.FC = (props) => {
  return (
    <UserContainer.Provider>
      <Screen />
    </UserContainer.Provider>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

でラップされた配下のコンポーネントで
Containerの情報を使うことができます。

あるコンポーネントからContainerにアクセスしたい場合はuseContainerという関数を使い利用したいContainerを呼び出します。

src/main.tsx
// 例
const userContainer = useContainer(UserContainer)

userContainer.user
// => { name: "" }

userContainer.setUser({ name: "Tom"} )

userContainer.name
// => { name: "Tom" }

ここで出てくる nameとsetNameは、先程UserContainer内のuseUserの返り値のオブジェクトです。

src/containers/UserContainer.ts
const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  // この返り値のオブジェクトがuseContainer(userContainer)に返される。
  return { user, setUser }
};

うごくものができました!

Image from Gyazo

Tips

ここからはより実践的なunstated-nextの使い方について書きます。

複数のContainerを定義したい。

1つのContainerだけで状態管理をしているとContainerが肥大化して可読性,保守性が下がります。そんな時はContainerを分割してしまいましょう。

新たにPlanContainerを定義します。

src/containers/PlanContainer.ts
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type Plan = {
  planID: string,
  planName: string,
}

const initialPlan: Plan = { planID: "1234567", planName: "Basic Plan" };

const usePlan = () => {
  const [plan, setPlan] = useState<Plan>(initialPlan);

  return { plan, setPlan }
};

const PlanContainer = createContainer(usePlan);

export { PlanContainer }

呼び出しもとで行うことは非常に簡単です。
Providerをネストして書くだけで複数のcontainerにアクセス可能になります。

main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { GeneralContainer } from "./containers/GeneralContainer";
import { UserContainer } from "./containers/UserContainer"
import { PlanContainer } from "./containers/PlanContainer";

const App: React.FC = (props) => {
  return (
    <UserContainer.Provider>
      <PlanContainer.Provider> // 追加!
        <Screen />
      </PlanContainer.Provider>
    </UserContainer.Provider>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)
  const planContainer = useContainer(PlanContainer) // アクセスできる!

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
      <p>Your Plan is {planContainer.plan.planName}({planContainer.plan.planID})</p>
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

Containerが大量に増えたときにネスト地獄したくない

そのとおりです。複数のContainerを利用しようとするとProviderの呼び出し元のネストが重なり、いらいらしてきます。

example.tsx
<User.Provider>
  <Auth.Provider>
    <Plan.Provider>
      <EventLog.Provider>
         // ... ネスト地獄
      </EventLog.Provider>
    </Plan.Provider>
  </Auth.Provider>
</UserProvider>

unstated-nextは現時点ではこれに対する解決策を用意していませんが
自作関数を定義することで可読性を改善することができます。

以下のcomposeという関数は、配列として与えられたContainerをネストしたJSXとして返却するシンプルな関数です。

compose.tsx
import * as React from "react";
import { Container } from "unstated-next";

const compose = (containers: Array<Container<any, any>>, children: JSX.Element): JSX.Element => {
  return containers.reduce((acc, Container) => {
    return <Container.Provider>{acc}</Container.Provider>
  }, children)
}

export { compose }

この関数を使って、すべてのContainerをまとめる GeneralContainer を定義します。

src/containers/GeneralContainer.tsx
import { compose } from "./compose";

import { UserContainer } from "./UserContainer";
import { PlanContainer } from "./PlanContainer";

const GeneralContainer = (props): JSX.Element => {
  return (
    compose(
      [
        UserContainer,
        PlanContainer
      ],
      props.children
    )
  )
}

export { GeneralContainer }

これをmain.tsxから呼び出せば晴れて(見た目的には)ネスト解消です。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { GeneralContainer } from "./containers/GeneralContainer";
import { UserContainer } from "./containers/UserContainer"
import { PlanContainer } from "./containers/PlanContainer";

const App: React.FC = (props) => {
  return (
    <GeneralContainer>
      <Screen />
    </GeneralContainer>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)
  const planContainer = useContainer(PlanContainer)

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
      {/* <p>Your Plan is {planContainer.plan.planName}({planContainer.plan.planID})</p> */}
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

非同期処理

Reduxだと非同期処理のためのミドルウェアとしてredux-sagaやredux-sagaがありますが、unstated-nextで非同期処理を行いたい場合はcontainer内に自前で非同期関数を定義することで実現できます。

APIを叩いた後の処理などで使うケースがあると思います。

src/main.tsx
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type User = {
  name: string
}

const initialUser: User = { name: "" };

const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  // 2秒後に更新する関数
  const setUserAsync = (user: User) => {
    return new Promise((resolve) => {
      setTimeout(
        () => {
          setUser(user)
          resolve()
        },
        2000);
    });
  }

  return { user, setUser, setUserAsync }
};

const UserContainer = createContainer(useUser);

export { UserContainer }

参考文献

ReactHooks公式
公式が非常にわかりやすく網羅的なDocs, Tutorialを用意してくれているので、まだHooksに触ったことが無い方は目を通しましょう。手を動かしても賞味1時間程度で終わります。

様々な種類のHooksがありますが、ひとまずのところ以下の3つだけ理解しておけば大丈夫だと思います。

  • useState
  • useEffect
  • useContext

unstated-next
冒頭にも書きましたがソースコードの時点で38行しかないため、Documentも非常にシンプルです。この記事を読んだあとであれば5分程度で理解できると思います。せっかくなのでソースコードも目を通しておくと勉強になります。

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

unstated-nextでライトにReactの状態管理をしてみる【React/TypeScriptハンズオン付き】

目次

  • unstated-nextとは
  • unstated-nextの特徴
  • どんな人・どんなケースにオススメか
  • 10分ハンズオン(React.js / TypeScript / unstated-next)
  • Tips
    • 複数のContainerを定義する
    • Containerが増えたときネスト地獄を避ける
    • 非同期処理
  • 参考文献

対象読者

・redux触ってみて記述量の多さに圧倒されてしまったReact初心者
・ReactHooks大好きお兄さんお姉さん。

また、Reactを始めたばかりでHooksを触ったことがない人でもとりあえず動かせるようにハンズオンも容易しています。

unstated-nextとは

React HooksベースはReactの状態管理ライブラリです。

状態管理ライブラリといえば、reduxを筆頭にmobx, unduxなど様々なライブラリがありますが、その中でもこのunstated-nextの際立った特徴はそのシンプルさです。なんとソースコードが38行しかありません。それゆえに学習コストが非常に低く(*注1)、カスタマイズもしやすいというのがこのライブラリの最大の特徴だとおもっています。

個人的な複雑な状態管理を必要としない小規模のツール系のアプリならばReduxではなくこのunstated-nextをファーストチョイスとして良いと思うほど気に入っているライブラリです。

※注1: React Hooksを理解している前提となります

unstated-next
Image from Gyazo

unstated-nextの特徴

Pros

・ソースコードが非常に小さいので全容の把握が容易(全38行)
・従って学習コストが非常に低い
・書かなければいけないコードの行数が少ない
・React Hooksの薄い薄いラッパーなのでカスタマイズしやすい

Cons

・複雑な状態管理, 大規模アプリに向いているかは(筆者には)わからない。
・React Hooksを学ぶための学習コストは必要。Class構文に慣れている人は少し疲れるかも。

どんな人にオススメか

何を言っても状態管理を全て純粋なReactHooksで書けるのが大きいです。関数型が好きで classなんて書きたくない! という人は気に入ると思います。

また学習コストの低さ、記述のシンプルさ
複雑な状態管理が必要ないアプリ、小規模なアプリの開発に使うのであれば間違いなくファーストチョイスとして良いライブラリだと思います。Reduxはreducerを創るためにキーボードを叩く指が疲れてきますからね...

使い方

簡単な例としてUserに関するグローバルアクセス可能な状態をunstated-nextを使ってつくってみましょう。

10分ハンズオン

このハンズオンではReactの環境構築から行っていきます。

ソースコードはこちら。commitログをたどると手順がわかると思います。

最終的なディレクトリ構造は以下のようになります。

- dist
    - index.html
- src
    - containers
        - compose.tsx
        - GeneralContainer.ts
        - UserContainer.ts
        - PlanContainer.ts
    - main.tsx
- package-lock.json
- package.json
- tsconfig.json
- webpack.config.js

0. npmでReact/TypeScriptの開発に必要なライブラリを入れていきます。

mkdir unstated-next-sample
cd unstated-next-sample
npm init -y
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader
npm i -S react react-dom @types/react @types/react-dom

1. tsconfig.json と webpack.config.js と定義します

tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5",
    "module": "es2015",
    "jsx": "react",
    "moduleResolution": "node",
    "lib": [
      "es2020",
      "dom"
    ]
  }
}
webpack.config.js
module.exports = {
  mode: "development",

  entry: "./src/main.tsx",
  output: {
    path: `${__dirname}/dist`,
    filename: "main.js"
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader"
      }
    ]
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  devServer: {
    contentBase: `${__dirname}/dist`
  }
};

2. 簡単なReactComponentを定義しましょう。

src/main.tsxに簡単なReactComponentを定義しましょう。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

const App: React.FC = (props) => {
  return (
    <div>
      <h1>UnstatedNext!</h1>
    </div>
  )
}

+const app = document.getElementById("app")
+ReactDOM.render(<App />, app)

dist/index.htmlも作成します。

dist/index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>UnstatedNext</title>
</head>

<body>
  <div id="app"></div>
  <script src="main.js"></script>
</body>

</html>

3. webpack-dev-serverを起動しましょう。

package.jsonのscriptの部分に以下の記述を追加します。

package.json
{
  "scripts": {
    "start": "webpack-dev-server",
  },
}

ターミナルから npm start と入力すると開発サーバーが立ち上がるはずです。

4. unstated-nextをインストールします。

npm install -S unstated-next

5. 状態を保持するためのContainerを定義します。

試しにUser情報を保持・更新するためのUserContainerを定義してみましょう。ファイルの場所は src/containers/UserContainer.ts とします。

src/containers/UserContainer.ts
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type User = {
  name: string
}
const initialUser: User = { name: "" };

const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  return { user, setUser }
};

const UserContainer = createContainer(useUser);

export { UserContainer }

なんとunstated-nextはこれだけで状態管理が可能になります。

6. Containerを呼び出してみよう

ではUserContainerの状態にアクセスするために、少しmain.tsxを編集していきます。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { UserContainer } from "./containers/UserContainer"

const App: React.FC = (props) => {
  return (
    <UserContainer.Provider>
      <Screen />
    </UserContainer.Provider>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

でラップされた配下のコンポーネントで
Containerの情報を使うことができます。

あるコンポーネントからContainerにアクセスしたい場合はuseContainerという関数を使い利用したいContainerを呼び出します。

src/main.tsx
// 例
const userContainer = useContainer(UserContainer)

userContainer.user
// => { name: "" }

userContainer.setUser({ name: "Tom"} )

userContainer.name
// => { name: "Tom" }

ここで出てくる nameとsetNameは、先程UserContainer内のuseUserの返り値のオブジェクトです。

src/containers/UserContainer.ts
const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  // この返り値のオブジェクトがuseContainer(userContainer)に返される。
  return { user, setUser }
};

うごくものができました!

Image from Gyazo

Tips

ここからはより実践的なunstated-nextの使い方について書きます。

複数のContainerを定義する。

1つのContainerだけで状態管理をしているとContainerが肥大化して可読性,保守性が下がります。そんな時はContainerを分割してしまいましょう。

新たにPlanContainerを定義します。

src/containers/PlanContainer.ts
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type Plan = {
  planID: string,
  planName: string,
}

const initialPlan: Plan = { planID: "1234567", planName: "Basic Plan" };

const usePlan = () => {
  const [plan, setPlan] = useState<Plan>(initialPlan);

  return { plan, setPlan }
};

const PlanContainer = createContainer(usePlan);

export { PlanContainer }

呼び出しもとで行うことは非常に簡単です。
Providerをネストして書くだけで複数のcontainerにアクセス可能になります。

main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { GeneralContainer } from "./containers/GeneralContainer";
import { UserContainer } from "./containers/UserContainer"
import { PlanContainer } from "./containers/PlanContainer";

const App: React.FC = (props) => {
  return (
    <UserContainer.Provider>
      <PlanContainer.Provider> // 追加!
        <Screen />
      </PlanContainer.Provider>
    </UserContainer.Provider>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)
  const planContainer = useContainer(PlanContainer) // アクセスできる!

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
      <p>Your Plan is {planContainer.plan.planName}({planContainer.plan.planID})</p>
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

Containerが増えたときネスト地獄を避ける

複数のContainerを利用しようとするとProviderの呼び出し元のネストが重なり、いらいらしてきます。

example.tsx
<User.Provider>
  <Auth.Provider>
    <Plan.Provider>
      <EventLog.Provider>
         // ... ネスト地獄
      </EventLog.Provider>
    </Plan.Provider>
  </Auth.Provider>
</UserProvider>

unstated-nextは現時点ではこれに対する解決策を用意していませんが
自作関数を定義することで可読性を改善することができます。

以下のcomposeという関数は、配列として与えられたContainerをネストしたJSXとして返却するシンプルな関数です。

compose.tsx
import * as React from "react";
import { Container } from "unstated-next";

const compose = (containers: Array<Container<any, any>>, children: JSX.Element): JSX.Element => {
  return containers.reduce((acc, Container) => {
    return <Container.Provider>{acc}</Container.Provider>
  }, children)
}

export { compose }

この関数を使って、すべてのContainerをまとめる GeneralContainer を定義します。

src/containers/GeneralContainer.tsx
import { compose } from "./compose";

import { UserContainer } from "./UserContainer";
import { PlanContainer } from "./PlanContainer";

const GeneralContainer = (props): JSX.Element => {
  return (
    compose(
      [
        UserContainer,
        PlanContainer
      ],
      props.children
    )
  )
}

export { GeneralContainer }

これをmain.tsxから呼び出せば晴れて(見た目的には)ネスト解消です。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { GeneralContainer } from "./containers/GeneralContainer";
import { UserContainer } from "./containers/UserContainer"
import { PlanContainer } from "./containers/PlanContainer";

const App: React.FC = (props) => {
  return (
    <GeneralContainer>
      <Screen />
    </GeneralContainer>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)
  const planContainer = useContainer(PlanContainer)

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
      {/* <p>Your Plan is {planContainer.plan.planName}({planContainer.plan.planID})</p> */}
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

非同期処理

Reduxだと非同期処理のためのミドルウェアとしてredux-sagaやredux-sagaがありますが、unstated-nextで非同期処理を行いたい場合はcontainer内に自前で非同期関数を定義することで実現できます。

APIを叩いた後の処理などで使うケースがあると思います。

src/main.tsx
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type User = {
  name: string
}

const initialUser: User = { name: "" };

const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  // 2秒後に更新する関数
  const setUserAsync = (user: User) => {
    return new Promise((resolve) => {
      setTimeout(
        () => {
          setUser(user)
          resolve()
        },
        2000);
    });
  }

  return { user, setUser, setUserAsync }
};

const UserContainer = createContainer(useUser);

export { UserContainer }

参考文献

ReactHooks公式
公式が非常にわかりやすく網羅的なDocs, Tutorialを用意してくれているので、まだHooksに触ったことが無い方は目を通しましょう。手を動かしても賞味1時間程度で終わります。

様々な種類のHooksがありますが、ひとまずのところ以下の3つだけ理解しておけば大丈夫だと思います。

  • useState
  • useEffect
  • useContext

unstated-next
冒頭にも書きましたがソースコードの時点で38行しかないため、Documentも非常にシンプルです。この記事を読んだあとであれば5分程度で理解できると思います。せっかくなのでソースコードも目を通しておくと勉強になります。

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

unstated-nextでシンプルなReact状態管理【React/TypeScriptハンズオン付き】

目次

  • unstated-nextとは
  • unstated-nextの特徴
  • 10分ハンズオン(React.js / TypeScript / unstated-next)
  • Tips
    • 複数のContainerを定義する
    • Containerが増えたときネスト地獄を避ける
    • 非同期処理
  • 参考文献

対象読者

・redux触ってみて記述量の多さに圧倒されてしまったReact初心者
・ReactHooks大好きお兄さんお姉さん。

また、Reactを始めたばかりでHooksを触ったことがない人でもとりあえず動かせるようにハンズオンも容易しています。

unstated-nextとは

React HooksベースはReactの状態管理ライブラリです。

状態管理ライブラリといえば、reduxを筆頭にmobx, unduxなど様々なライブラリがありますが、その中でもこのunstated-nextの際立った特徴はそのシンプルさです。なんとソースコードが38行しかありません。それゆえに学習コストが非常に低く(*注1)、カスタマイズもしやすいというのがこのライブラリの最大の特徴だとおもっています。

個人的な複雑な状態管理を必要としない小規模のツール系のアプリならばReduxではなくこのunstated-nextをファーストチョイスとして良いと思うほど気に入っているライブラリです。

※注1: React Hooksを理解している前提となります

unstated-next
Image from Gyazo

unstated-nextの特徴

Pros

・ソースコードが非常に小さいので全容の把握が容易(全38行)
・従って学習コストが非常に低い
・書かなければいけないコードの行数が少ない
・React Hooksの薄い薄いラッパーなのでカスタマイズしやすい

Cons

・複雑な状態管理, 大規模アプリに向いているかは(筆者には)わからない。
・React Hooksを学ぶための学習コストは必要。Class構文に慣れている人は少し疲れるかも。

10分ハンズオン

このハンズオンではReactの環境構築から行っていきます。

ソースコードはこちら。commitログをたどると手順がわかると思います。

最終的なディレクトリ構造は以下のようになります。

- dist
    - index.html
- src
    - containers
        - compose.tsx
        - GeneralContainer.ts
        - UserContainer.ts
        - PlanContainer.ts
    - main.tsx
- package-lock.json
- package.json
- tsconfig.json
- webpack.config.js

0. npmでReact/TypeScriptの開発に必要なライブラリを入れていきます。

mkdir unstated-next-sample
cd unstated-next-sample
npm init -y
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader
npm i -S react react-dom @types/react @types/react-dom

1. tsconfig.json と webpack.config.js と定義します

tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5",
    "module": "es2015",
    "jsx": "react",
    "moduleResolution": "node",
    "lib": [
      "es2020",
      "dom"
    ]
  }
}
webpack.config.js
module.exports = {
  mode: "development",

  entry: "./src/main.tsx",
  output: {
    path: `${__dirname}/dist`,
    filename: "main.js"
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader"
      }
    ]
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  devServer: {
    contentBase: `${__dirname}/dist`
  }
};

2. 簡単なReactComponentを定義しましょう。

src/main.tsxに簡単なReactComponentを定義しましょう。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

const App: React.FC = (props) => {
  return (
    <div>
      <h1>UnstatedNext!</h1>
    </div>
  )
}

+const app = document.getElementById("app")
+ReactDOM.render(<App />, app)

dist/index.htmlも作成します。

dist/index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>UnstatedNext</title>
</head>

<body>
  <div id="app"></div>
  <script src="main.js"></script>
</body>

</html>

3. webpack-dev-serverを起動しましょう。

package.jsonのscriptの部分に以下の記述を追加します。

package.json
{
  "scripts": {
    "start": "webpack-dev-server",
  },
}

ターミナルから npm start と入力すると開発サーバーが立ち上がるはずです。

4. unstated-nextをインストールします。

npm install -S unstated-next

5. 状態を保持するためのContainerを定義します。

試しにUser情報を保持・更新するためのUserContainerを定義してみましょう。ファイルの場所は src/containers/UserContainer.ts とします。

src/containers/UserContainer.ts
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type User = {
  name: string
}
const initialUser: User = { name: "" };

const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  return { user, setUser }
};

const UserContainer = createContainer(useUser);

export { UserContainer }

なんとunstated-nextはこれだけで状態管理が可能になります。

6. Containerを呼び出してみよう

ではUserContainerの状態にアクセスするために、少しmain.tsxを編集していきます。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { UserContainer } from "./containers/UserContainer"

const App: React.FC = (props) => {
  return (
    <UserContainer.Provider>
      <Screen />
    </UserContainer.Provider>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

でラップされた配下のコンポーネントで
Containerの情報を使うことができます。

あるコンポーネントからContainerにアクセスしたい場合はuseContainerという関数を使い利用したいContainerを呼び出します。

src/main.tsx
// 例
const userContainer = useContainer(UserContainer)

userContainer.user
// => { name: "" }

userContainer.setUser({ name: "Tom"} )

userContainer.name
// => { name: "Tom" }

ここで出てくる nameとsetNameは、先程UserContainer内のuseUserの返り値のオブジェクトです。

src/containers/UserContainer.ts
const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  // この返り値のオブジェクトがuseContainer(userContainer)に返される。
  return { user, setUser }
};

うごくものができました!

Image from Gyazo

Tips

ここからはより実践的なunstated-nextの使い方について書きます。

複数のContainerを定義する。

1つのContainerだけで状態管理をしているとContainerが肥大化して可読性,保守性が下がります。そんな時はContainerを分割してしまいましょう。

新たにPlanContainerを定義します。

src/containers/PlanContainer.ts
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type Plan = {
  planID: string,
  planName: string,
}

const initialPlan: Plan = { planID: "1234567", planName: "Basic Plan" };

const usePlan = () => {
  const [plan, setPlan] = useState<Plan>(initialPlan);

  return { plan, setPlan }
};

const PlanContainer = createContainer(usePlan);

export { PlanContainer }

呼び出しもとで行うことは非常に簡単です。
Providerをネストして書くだけで複数のcontainerにアクセス可能になります。

main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { GeneralContainer } from "./containers/GeneralContainer";
import { UserContainer } from "./containers/UserContainer"
import { PlanContainer } from "./containers/PlanContainer";

const App: React.FC = (props) => {
  return (
    <UserContainer.Provider>
      <PlanContainer.Provider> // 追加!
        <Screen />
      </PlanContainer.Provider>
    </UserContainer.Provider>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)
  const planContainer = useContainer(PlanContainer) // アクセスできる!

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
      <p>Your Plan is {planContainer.plan.planName}({planContainer.plan.planID})</p>
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

Containerが増えたときネスト地獄を避ける

複数のContainerを利用しようとするとProviderの呼び出し元のネストが重なり、いらいらしてきます。

example.tsx
<User.Provider>
  <Auth.Provider>
    <Plan.Provider>
      <EventLog.Provider>
         // ... ネスト地獄
      </EventLog.Provider>
    </Plan.Provider>
  </Auth.Provider>
</UserProvider>

unstated-nextは現時点ではこれに対する解決策を用意していませんが
自作関数を定義することで可読性を改善することができます。

以下のcomposeという関数は、配列として与えられたContainerをネストしたJSXとして返却するシンプルな関数です。

compose.tsx
import * as React from "react";
import { Container } from "unstated-next";

const compose = (containers: Array<Container<any, any>>, children: JSX.Element): JSX.Element => {
  return containers.reduce((acc, Container) => {
    return <Container.Provider>{acc}</Container.Provider>
  }, children)
}

export { compose }

この関数を使って、すべてのContainerをまとめる GeneralContainer を定義します。

src/containers/GeneralContainer.tsx
import { compose } from "./compose";

import { UserContainer } from "./UserContainer";
import { PlanContainer } from "./PlanContainer";

const GeneralContainer = (props): JSX.Element => {
  return (
    compose(
      [
        UserContainer,
        PlanContainer
      ],
      props.children
    )
  )
}

export { GeneralContainer }

これをmain.tsxから呼び出せば晴れて(見た目的には)ネスト解消です。

src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { useContainer } from 'unstated-next';
import { GeneralContainer } from "./containers/GeneralContainer";
import { UserContainer } from "./containers/UserContainer"
import { PlanContainer } from "./containers/PlanContainer";

const App: React.FC = (props) => {
  return (
    <GeneralContainer>
      <Screen />
    </GeneralContainer>
  )
}

const Screen: React.FC = (props) => {
  const userContainer = useContainer(UserContainer)
  const planContainer = useContainer(PlanContainer)

  const onChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const user = { ...userContainer.user, name: e.target.value }
    userContainer.setUser(user);
  }

  return (
    <div>
      <h1>UnstatedNext!</h1>
      <p>{userContainer.user.name}</p>
      <input onChange={onChangeInput} />
      <p>Your Plan is {planContainer.plan.planName}({planContainer.plan.planID})</p>
    </div>
  )
}

const app = document.getElementById("app")
ReactDOM.render(<App />, app)

非同期処理

Reduxには非同期処理を行うためのミドルウェアとしてredux-sagaやredux-sagaがありますが、unstated-nextで非同期処理を行いたい場合はcontainer内に自前で非同期関数を定義することで実現できます。

APIを叩いた後の処理などで使うケースがあると思います。

src/main.tsx
import { useState } from 'react';
import { createContainer } from 'unstated-next';

type User = {
  name: string
}

const initialUser: User = { name: "" };

const useUser = () => {
  const [user, setUser] = useState<User>(initialUser);

  // 2秒後に更新する関数
  const setUserAsync = (user: User) => {
    return new Promise((resolve) => {
      setTimeout(
        () => {
          setUser(user)
          resolve()
        },
        2000);
    });
  }

  return { user, setUser, setUserAsync }
};

const UserContainer = createContainer(useUser);

export { UserContainer }

参考文献

ReactHooks公式
公式が非常にわかりやすく網羅的なDocs, Tutorialを用意してくれているので、まだHooksに触ったことが無い方は目を通しましょう。手を動かしても賞味1時間程度で終わります。

様々な種類のHooksがありますが、ひとまずのところ以下の3つだけ理解しておけば大丈夫だと思います。

  • useState
  • useEffect
  • useContext

unstated-next
冒頭にも書きましたがソースコードの時点で38行しかないため、Documentも非常にシンプルです。この記事を読んだあとであれば5分程度で理解できると思います。せっかくなのでソースコードも目を通しておくと勉強になります。

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