Closed4

Reactのレンダリングについてメモ

水無瀬水無瀬

こういうコードだとstateが変わるたびにInputとButtonが再レンダリングされる。
親コンポーネント(InputButton)が再レンダリングされるので正しい挙動だけど無駄なはず🤔

InputButton.tsx
import { Button } from '../../../components/button/Button';
import { Input } from '../../../components/input/Input';
import { useDispatch } from 'react-redux';
import { ChangeEvent, FormEvent, useState } from 'react';
import { addList } from '../../../store';

export const InputButton = () => {
  console.log('in InputButton');
  const dispatch = useDispatch();
  const [name, setName] = useState<string | undefined>();
  const changeName = (event: ChangeEvent<HTMLInputElement>) => {
    console.log('changeName: ', name);
    setName(event.target.value);
  }
  const [age, setAge] = useState<number | undefined>();
  const changeAge = (event: ChangeEvent<HTMLInputElement>) => {
    console.log('changeAge: ', age);
    setAge(Number(event.target.value) || 0);
  }

  const buttonClick = (event: FormEvent<HTMLButtonElement>) => {
    console.log('buttonClick');
    console.log(`name: ${name} / age: ${age}`);
    
    if (!name || !age) {
      return;
    }
    dispatch(addList({name, age}));
  }
  return (<>
    <label> name: 
      <Input type='text' onChange={changeName}/>
    </label>
    <label> age: 
      <Input type='number' onChange={changeAge}/>
    </label>
    <Button text='追加' onClick={buttonClick}/>
  </>)
}
水無瀬水無瀬

InputとButtonをメモ化してみる

してみるがこれではダメ。
親コンポーネントに引きづられて再レンダリングされる。

Input.tsx
import { ChangeEvent, useMemo } from "react";

type InputProps = {
  type: 'text' | 'number',
  placeHolder?: string,
  onChange?: (event: ChangeEvent<HTMLInputElement>) => void
};

export const Input: React.FC<InputProps> = ({type, placeHolder, onChange}) => {
  return useMemo(() => {
    console.log(`in Input. type is ${type}`);
    return <input type={type} placeholder={placeHolder} onChange={onChange}/>
  }, [type, placeHolder, onChange]);
}
Button.tsx
import { FC, FormEvent, useMemo } from 'react';
import './button.css';

type ButtonProps = {
  text: string;
  onClick?: (event: FormEvent<HTMLButtonElement>) => void;
}

export const Button: FC<ButtonProps> = (props) => {
  return useMemo(() => {
    console.log(`in Button. text is ${props.text}`);
    return <button onClick={props.onClick}>{props.text}</button>;
  }, [props]);
}
水無瀬水無瀬

渡している関数もメモ化する

ここまでやってInputについては無駄なレンダリングがなくなる。
Buttonnameageが変更される度にbuttonClickが更新されるので、この書き方しても無駄。

InputButton.tsx
import { Button } from '../../../components/button/Button';
import { Input } from '../../../components/input/Input';
import { useDispatch } from 'react-redux';
import { ChangeEvent, FormEvent, useCallback, useState } from 'react';
import { addList } from '../../../store';

export const InputButton = () => {
  console.log('in InputButton');
  const dispatch = useDispatch();
  const [name, setName] = useState<string | undefined>();
  const changeName = useCallback((event: ChangeEvent<HTMLInputElement>) => {
    console.log('changeName');
    setName(event.target.value);
  }, []); 
  // ここから
  const [age, setAge] = useState<number | undefined>();
  const changeAge = useCallback((event: ChangeEvent<HTMLInputElement>) => {
    console.log('changeAge');
    setAge(Number(event.target.value) || 0);
  }, []);
  const buttonClick = useCallback((event: FormEvent<HTMLButtonElement>) => {
    console.log('buttonClick');
    console.log(`name: ${name} / age: ${age}`);
    
    if (!name || !age) {
      return;
    }
    dispatch(addList({name, age}));
  }, [name, age]);
  // ここまでを変更
  return (<>
    <label> name: 
      <Input type='text' onChange={changeName}/>
    </label>
    <label> age: 
      <Input type='number' onChange={changeAge}/>
    </label>
    <Button text='追加' onClick={buttonClick}/>
  </>)
}
このスクラップは2020/12/21にクローズされました