Hero img
Reactのstateと仲良くなるために

useState useCallback useMemoの理解を深めた

reactでログを確認したところ、かなり無駄にレンダーされている事が分かったので、どうにかしないといけないと思いuseState useCallback useMemoを改めて勉強した。


目次

  • React useStateを学ぶきっかけ
  • 環境条件
  • useState
  • 一般的な使用用法
  • 初期値を指定する
  • 値の変更
  • レンダー
  • object arrayのレンダー
  • レンダーを制御する
  • useRefを使う
  • usememo memo
  • memo
  • memo使ってもレンダーが開始される
  • useCallback
  • 最初躓いた点
  • 親の中にmemoのコンポーネント追加
  • usestate更新時レンダーが始まる
  • usestateで値を保持するだけ機能が欲しかったが、値が更新されるとレンダーが開始されてしまった。
  • memoを使えばとりあえずOKだ?
  • memoはとりあえず比較する?
  • callback
  • まとめ

React useStateを学ぶきっかけ

Reactに初めて触れたときuseStateは変数の役割をしてくれるという認識だけでした。しかし、axios等で外からか呼び出すPOST GET通信の利用が増えると、どうしてもパフォーマンスを気を付ける状況が出てきて、できるだけ外との通信回数を減らしたい、レンダー時間を減らしたいと思い頑張りました。
参考書等を買ってはみたもののこのレンダーパフォーマンスに関して深く書かれていなかったため手を動かしながら学ぶことにしました。
参考書は買って満足してしまうタイプです。

環境条件

typescriptとreact18.2.0の環境で実行しました。 プロジェクトの作成はcreate-react-appで作ったものです

 npx create-react-app testproject --template typescript  
  • react@18.2.0
  • typescript@4.8.2

useState

一般的な使用用法

まず一般的な使用用法として[state,setstate]=useState<type>(hoge)という形になる。
始めた当初set〇〇とはなんだよとか思っていたが、なんでもいいことが分かった。
typescriptに関しても型を指定してあげる方法も分からなかった。

function App() {
  const [count, setCount] = useState<number>(0);
  const [value, setValue] = useState<string>();
  const [idlist, setidlist] = useState<publishers>({});
  ...
}

初期値を指定する

stateの初期値は指定できるのは分かっていたのだが関数の結果を指定することが新たに分かった

//初期値を設定できる。一回だけ発生する。
  const [state, setState] = useState(() => {
    const initialState = tfunction("テキスト");
    return initialState;
  });
  function tfunction(props:string){
    return props+1;
  }

//テキスト1が設定される。

値の変更

state値の変更もsetを使う。ではなく右の文字を使うためsetで無くても大丈夫だった。紛らわしくなるのでsetにする方が後々いいと思う。

const [value, setValue] = useState<string>("");
setValue("testValue");
const [tvalue, ontValue] = useState<string>("");//<-これでもできるが...
ontValue("Onvalue");

レンダー

useState値が更新された時画面がレンダリングされてしまう。そのため画面更新が不要でもも全てが必ずレンダリングされて困ってしまった。
レンダリング対象になるのは親+子のコンポーネント。つまり何も考えずに作ってしまうとstateが更新されるごとに全てを再レンダリングしてしまう。

function App() {
  const [count, setCount] = useState<number>(0);
  const [value, setValue] = useState<string>();
  const [idlist, setidlist] = useState<publisher>({});//<--レンダ対象外
  ...
  return(<><component/>...</>)
}
export default App;

function component() {//これも再レンダリングされてしまう
  const [Componentcount, setComponentCount] = useState<number>(0);
  ...
    return(<>...</>)
}
/*
count,value,どれかが更新されるだけでApp()内の物が再レンダリングされてしまう。
また、その中にcomponentもあるのでこれもレンダリングの対象になる。
*/

object arrayのレンダー

stateのタイプがobjectかarrayの場合更新しても画面が変わらない模様。それでもレンダリングさせたい場合。新たに変数を作れば可能。強引なやり方かも

interface publisher {
  name: string,
}
  const [idlist, setidlist] = useState<publisher>();
    let tmpobj: publisher = Object.assign({}, idlist);
    setidlist(tmpobj);

レンダーを制御する

今回の目標であるレンダー回数を制御するためにはどうすればいいのか?自分なりにまとめました。

  • useRef
  • memo
  • callback

useRefを使う

そもそも値だけを更新したい場合はuseRefを使うuseRefは値が更新されてもレンダーが開始されない。
useStateは必ずレンダーが開始すると覚えておく。 逆にuseStateの場合は更新されたらレンダーが開始してしまうので、変数の値を持たせたいだけの場合はuseRefを使えば解決する。

const [stateid, setstateid] = useState<string>("");
const refid = useRef<string>("")

//更新
setstateid("updatesate");//<--レンダー実行
refid.current ="updatesate";//<--レンダーされない

usememo memo

書き方の問題だと思ってましたがusememoとmemo二つが存在している。

memo

  const atestfunction =((id:string)=>{
    console.log(id);
  })

    return (
    <div>
      <Test1/>
      <Test2 tekisuto={"inputtext"}/>
      <Test3 Ttestfunction={atestfunction} />
    </div>
  );
  ....
const Test1 = memo(() =>{
  console.log("TEST1")
  return(<div>Test1</div>)});

  const Test2 = memo((props:{tekisuto:string}) =>{
    console.log("TEST2")
    return(<div>{props.tekisuto}</div>)});

  const Test3 = memo<any>((props:{Ttestfunction:(id:string)=>void}) =>{
    console.log("TEST3")
    return(<button onClick={(()=>{props.Ttestfunction("value")})}>button</button>)});

typeを使えばまだ綺麗になるのだがtypeを追加しないanyを使わないパターンがあれば知りたい。

type newprops={
  tekisuto:string
}
function App() {
  //...
  return
  <>
      <Test2 tekisuto={"inputtext"}/>
</>
//...
}
export default App;

const Test2 = memo((props:newprops) =>{
    console.log("TEST2")
    return(<div>{props.tekisuto}</div>)});

memo使ってもレンダーが開始される

memo使用したが関数を渡すとレンダーが開始されてしまうことが分かった。 memoはprops「tekisuto , Ttestfunction」が変わった場合のみレンダーが開始するが、関数を指定するとpropsが変わったとみなされるためレンダーが開始してしまう。(Test3) 関数を入れても内容が一緒なのでレンダーしたくない。それを回避するためにuseCallbackがある。

useCallback

memoを使ってもレンダリングされてしまうしまう場合はcallbackを使用すれば解決でき可能性がある。
関数を普通に渡してしまうとレンダリングされてしまうが、一度callbackを挟むことで関数で比較し得てくれるため、同じだった場合レンダーはされない。
そして、[]内にstateを入れることでそのstateが変更されたタイミングでレンダーの開始をしてくれるようにもなる。入れる必要がある状況はどんな状況なのだろうか?

  const [count, setCount] = useState<number>(0);
  const atestfunction =((id:string)=>{console.log(id)});
//const callbackfunction = useCallback((id:string) => atestfunction(id),[]);
  const callbackfunction = useCallback((id:string) => atestfunction(id),[count]);

  <Test3 Ttestfunction={callbackfunction} />
  //変更した部分はatestfunctionを呼び出すcallbackのcallbackfunctionを定義して      
  //<Test3 Ttestfunction={callbackfunction} />に先ほど定義したcallbackを入れるだけ

memoとcallbackを使用してやっとレンダーを回避することができた。
私がやりたかったレンダリング回避は実現ができた。

最初躓いた点

始めて触った時つまづいた点がたくさんありました。

親の中にmemoのコンポーネント追加

state更新時はそのコンポーネント内部のものを再レンダリングしてしまうためその中でmemoを定義してもレンダーしてしまったので全く意味のないことをしていました。

//fucntion Appの内部にmemo化したものを入れる
function App() {
return(
  <Test1/>
)
  const Test1 = memo(() =>{
  console.log("TEST1")
  return(<div>Test1</div>)});
}

正しく動作させるためには外に書かないとだめだった。

//fucntion Appの内部にmemo化したものを入れる
function App() {
return(
  <Test1/>
)
}
  const Test1 = memo(() =>{
  console.log("TEST1")
  return(<div>Test1</div>)});

usestate更新時レンダーが始まる

usestateで値を保持するだけ機能が欲しかったが、値が更新されるとレンダーが開始されてしまった。

stateが更新されたらレンダーが必ず始まる。ということを早めに知りたかった。 解決策とすればuseRefを使用する。

memoを使えばとりあえずOKだ?

React.memoを使用すればレンダー制御は万全だと思った、しかし関数、ファンクションを入れてしまうと再レンダリングしてしまうので意味が分からなかった。callbackを使用する必要がある。

memoはとりあえず比較する?

memoはJSXを比較して異なった場合だと思っていたのだが、porpsで渡った物が異なった場合だった。

const articlestab1 = <p>hogehoge</p>
const articlestab2 = <p>piyopiyo</p>
let memotag = articlestab1;
memotag = articlestab2;
//JSX等で作られたhtmlのelementが異なった場合のみレンダーされると思っていた。

callback

callbackはmemo化したコンポーネントを入れるものだと思っていた
完全に間違っているとは言えない?ちゃんと動作するがどの場面で使用するのだろう?

  const ReactmemoC = memo(()=>{
    console.log(count);
    return(<div>{count}</div>)})
  const Hogecallback = useCallback(ReactmemoC,[count]);
  return(
    ...
          <Hogecallback/>
  )

まとめ

今でもまだまだ理解ができていないところが多いです。
今回stateについて調べたのでreact触り始めた当初は本当に色々と問題あるコードを書いていた事が分かった。ちゃんと公式ページと、サンプルを比較しながら見なかったことが原因だ。サンプル等があればプロジェクトを新規で作成し、ちゃんと見て完全にコピペを実行してテストを行う必要があると感じた。
今回かなりの間違いはmemoをApp function内に記入したこと、usesateをとりあえず使っていたことだ。memo , callbackを今回しっかり学べたので時間はかかってしまったが、少しだけReactと仲良くすることができた気がする。

関連記事

コメント

コメントを書く

質問や、間違いがありましたら、お気軽にどうぞ

※お名前とコメントの内容を入力してください。
※全てこのブログ上に公表されます。
※許可なく管理者によって修正・削除する場合がございます。 詳しくはプライバシーポリシーを参照ください