作成日:2023-10-12 ・更新日:2023-10-20
reactのグローバルステートのライブラリーであるRecoilを使って見ました,とてもいい感じですが、再レンダリングが発生することが多く困った事になり、selectorやselectorFamilyを使うことでレンダリングを抑制出来ました。
React recoilは比較的に新しく登場した、状態管理ライブラリーです。グローバルステートの状態管理です。
状態管理ライブラリーはRedux かContext APIが有名どころ単純なアプリであれば、Contextで代用できるが、ソースコードが膨大になって来ると、ちょっと困ったりするので、状態管理のライブラリーはとりあえずReduxかRecoilを使えば良いのではないか? Reduxは個人的には使いづらかったが、個人差なので...
recoilを使うためには
RecoilRoot
で囲む私は、ちょっと??!Atomってなんだよ。ってなってしまいましたが、Recoilを使う上で一番理解しないといけない部分だった。(後程補足します。)
インストールはコマンド一行。
npm install recoil
次にrecoilをインポートする。
ここは公式と異なった書き方をしました。
公式はapp.jsに書いていますが、index.jsに書きます。
<App/>を<RecoilRoot>
囲むだけなのでどちらでも動きますが、index.jsに書いた方が良いんじゃないかな?
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { RecoilRoot } from 'recoil'; // RecoilRootをインポートしていることを確認
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
/*
<App/>となっているところを<RecoilRoot>で囲むだけです。
*/
適当なjsファイルを作成します。
公式のコードをちょこっと修正しています。公式ではimportが書かれていませんね....
import { atom } from "recoil";
export const TextState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: 'Defaulttext', // default value (aka initial value)
});
recoilはusestateとほとんど同じ使い方なのでそこまで難しくないと思いますが、先ほど作ったatom.jsをインポートする必要がありますのでその点だけ注意ください。
import {useRecoilState} from "recoil";
import {TextState} from "./atom";
function App() {
const [text, setText] = useRecoilState(TextState);
return(
<>
<p>
{text}
</p>
</>
)
}
export default App
npm run start
ブラウザーで確認するとatomのdefaultに書いた'Defaulttext'がちゃんと取得できます。
usestateと同じように使うため比較的に分かりやすいですね。
import {useRecoilState} from "recoil";
import {TextState} from "./atom";
function App() {
const [text, setText] = useRecoilState(TextState);
const ButtonClick = (()=>{
setText(new Date().toString())//ここ
})
return(
<>
<button onClick={ButtonClick}>ChangeRecoilStates</button>
<p>
{text}
</p>
</>
)
}
export default App
とりあえ動かすことができた。
atomとは何か?
データストアとか単位とか状態とか色々書かれているがしっくりこない。
要するにグローバルステート。つまりvar TextState ="Defaulttext"
というグローバル変数ではないのか?
結局何? == グローバルステートの箱?
usesateと同様に値が更新されると、再レンダリングが発生するので、recoilを使う場合は再レンダリングを気にしないといけない。
先ほどのapp.jsを少し変えてみた。
import {useRecoilState} from "recoil";
import {TextState} from "./atom";
function App() {
const [text, setText] = useRecoilState(TextState);
const ButtonClick = (()=>{
setText(new Date().toString())
})
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<>
<button onClick={ButtonClick}>ChangeRecoilStates</button>
<p style={style}>
文字色が変わればレンダリングされている
</p>
</>
)
}
export default App
先のレンダリングされる現象は、useRecoilStateを使うと、値を取得して使わない場合であっても再レンダリングが発生してしまいます。
そこで登場するのがuseSetRecoilStateとuseRecoilValueです。
useRecoilStateは値の変更と取得の両方の機能をもっていますが、
更新のコードと、取得のコードが別々になっています。 そのため、useSetRecoilStateを使えば、値の変更ではレンダリングが発生しません。
import {useRecoilValue ,useSetRecoilState} from "recoil";
import {TextState} from "./atom";
function App() {
const asetText = useSetRecoilState(TextState);
const ButtonClick = (()=>{
asetText(new Date().toString())
})
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<>
<button onClick={ButtonClick}>ChangeRecoilStates</button>
<p style={style}>
文字色が変わればレンダリングされている
</p>
<Appdemo/>
</>
)
}
const Appdemo=(()=>{
const gettext = useRecoilValue(TextState);
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<p style={style}>
子のコンポーネントです:{gettext}
</p>
)
})
export default App
画像のように親のは変わらず、子だけが再レンダリングされていることが分かります。
グローバルステートを使う時は単一の文字データだけではなく、objectやarrayを使いたいことがほとんどだと思います。
import { atom } from "recoil";
export const TextState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: 'Defaulttext', // default value (aka initial value)
});
//以下を追加
export const Userinfo = atom({
key: 'yumenoinfo', // unique ID (with respect to other atoms/selectors)
default: {
name:"yumeno",
id:"001",
password:"abcd",
country:"jp",
},
});
//Appdemoのコンポーネントを修正
const Appdemo = (() => {
const gettext = useRecoilValue(TextState);
const yumenoinfo = useRecoilValue(Userinfo);
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return (
<div>
<p style={style}>
子のコンポーネントです:{gettext}
</p>
<ul>
<li>名前:{yumenoinfo.name}</li>
<li>ID:{yumenoinfo.id}</li>
<li>パスワード:{yumenoinfo.password}</li>
<li>国:{yumenoinfo.country}</li>
</ul>
</div>
)
})
ではobjectの値の更新と取得をするには?
どうすればいいのかというと
レンダリング結果が分かるようにコンポーネントを小分けします。
無駄にコードを分けているので長くなって申し訳ないです。
import { useRecoilValue, useSetRecoilState , useRecoilState } from "recoil";
import { TextState, Userinfo } from "./atom";
function App() {
const asetText = useSetRecoilState(TextState);
const ButtonClick = (() => {
asetText(new Date().toString())
})
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return (
<>
<button onClick={ButtonClick}>ChangeRecoilStates</button>
<p style={style}>
文字色が変わればレンダリングされている
</p>
<Appdemo />
<NameComponent/>
<IDComponent/>
<PWComponent/>
<CountryComponent/>
</>
)
}
const Appdemo = (() => {
const gettext = useRecoilValue(TextState);
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
const [yumenoinfo,setyumenoinfo] = useRecoilState(Userinfo);
const ChangeYumenoid = (()=>{
setyumenoinfo({...yumenoinfo , id : "002"})
})
return (
<div>
<button onClick={(()=>ChangeYumenoid())}>YumenoidChange</button>
<p style={style}>
子のコンポーネントです:{gettext}
</p>
</div>
)
})
const NameComponent = () =>{
const yumenoinfo = useRecoilValue(Userinfo);
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<p style={style}>
名前:{yumenoinfo.name}
</p>
)
}
const IDComponent = () =>{
const yumenoinfo = useRecoilValue(Userinfo);
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<p style={style}>
ID:{yumenoinfo.id}
</p>
)
}
const PWComponent = () =>{
const yumenoinfo = useRecoilValue(Userinfo);
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<p style={style}>
PW:{yumenoinfo.password}
</p>
)
}
const CountryComponent = () =>{
const yumenoinfo = useRecoilValue(Userinfo);
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<p style={style}>
国:{yumenoinfo.country}
</p>
)
}
export default App
IDだけを変更しているのに、名前、ID,PW,国の全てが再レンダリングされてしまっている.... これを解消できるのがselectorです。
objectでIDだけを変更したが、その他の全ても再レンダリングしてしまう。
そこでselectorまたはselectorFamilyを使います。
この2つの違いはselectorFamilyは引数を受け取ることができ、
selectorは引数を渡すことができません。
import { atom, selector , selectorFamily } from "recoil";//<--importする
export const TextState = atom({
key: 'textState',
default: 'Defaulttext',
});
export const Userinfo = atom({
key: 'yumenoinfo',
default: {
name:"yumeno",
id:"001",
password:"abcd",
country:"jp",
},
});
//selectorFamilyを追加
export const AtomselectorFamilydemo = selectorFamily({
key: 'getyumenoinfo' ,
get: (key) =>({get}) =>{
let yumenoinfo = get(Userinfo);
return yumenoinfo[key];
}
})
//selectorを追加
export const Getusercountry = selector({
key: 'getyumenoinfo' ,
get: ({get}) =>{
let yumenoinfo = get(Userinfo);
return yumenoinfo["name"];
}
})
app.jsも修正します。
//AtomselectorFailydemoをatom.jsで作成したので、インポートします。
import { TextState, Userinfo , AtomselectorFamilydemo } from "./atom";
const NameComponent = () =>{
const yumenoinfo = useRecoilValue(AtomselectorFamilydemo("name"));//<--引数を渡せる。ここではnameを渡している。
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<p style={style}>
名前:{yumenoinfo}
</p>
)
}
const CountryComponent = () =>{
const yumenoinfo = useRecoilValue(Getusercountry);//引数は渡せない。
const getColor = () => Math.floor(Math.random() * 255)
const style = {
color: `rgb(${getColor()},${getColor()},${getColor()})`,
}
return(
<p style={style}>
国:{yumenoinfo}
</p>
)
}
このようにselectorを使うことで、objectの一部が更新され、関係がない場合は再レンダリングが発生しないようになります。
gitに今回のプロジェクトファイルをアップしました。
reocilを使えばコンポーネント分かれていても値を取得でき、更新もすることができる。atomのjsを作り、importすれば何処のコンポーネントでもグローバルステートとして使えるので、個人的このライブラリーが使いやすい。
覚えて置く事は、recoilを使う為にはatomファイルをまず作成し、余計な再レンダリングが発生しないように、selectorを使う。 objectの一部を更新する場合はusesateと同様分割代入を使う。
質問や、間違いがありましたら、お気軽にどうぞ
※お名前とコメントの内容を入力してください。
※全てこのブログ上に公表されます。
※許可なく管理者によって修正・削除する場合がございます。 詳しくはプライバシーポリシーを参照ください