Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

React Nativeで、いいねした時のタイムラグをなくす/componentWillReceivePropsをやめてgetDerivedStateFromPropsを使う

More than 1 year has passed since last update.

目的

ツイッターぽいいいねを実装する。
ツイッターていいね押すとすぐハートが赤くなるけど、(少し昔の)airbnbは赤くなるまで少し時間がかかる。
どちらが良いかという話ではなく、自分はツイッターみたくすぐ赤くしたかった。
これを実現するのが目的。

もう一つの話として、
reactのライフサイクルが新しくなる話。
https://mae.chab.in/archives/60040

今まで componentWillReceiveProps をダメと知りながらも愛用してきたが、やっぱり文句言われすぎて、手放すことにした。

上の記事によると、
componentWillMountcomponentWillReceivePropscomponentWillUpdate
と合わせて、api通信時に色々問題を起こす可能性があるかつ、使い方が明確でなく、混乱するユーザーが多くいたらしい。

ので書き換えることにした。
componentWillReceivePropsの代用はgetDerivedStateFromPropsでいけるらしい。

今まで

ツイッターみたいな「いいね」をreact native + rails + mysqlで実装するときcomponentWillReceivePropsを使っていた。
(いまどきfirebase使えという話ではあるが、、)

demo_181220.gif

流れ的には
アイテムをいいねする(ハートをonPress)
→apiにrequest
→サーバ側でこのアイテムのステータスを(いいねされた状態に)変える
→レスポンスを返す
→レスポンスが返ってきたらハートの色を変える

である。

このレスポンスが返ってきたとき、propsが変化するのでこれをcomponentWillReceivePropsでキャッチしていた。

具体的には以下。

good.js
import React, { Component } from 'react';
...

type Props = {
...
}

class Good extends Component<Props> {
 state = {
   isGood: false, <-- アイテムがいいねされているか
 }

 componentDidMount() {
  ... 
  this.setState({
    isGood, <--- 初期状態でapiからいいねされているか取ってきてsetStateする
  });
 }

 componentWillReceiveProps(nextProps) {
   const { isGood } = nextProps;
   this.setState({
     isGood,  <--- いいねした時apiからレスポンス返ってきたらsetStateする
   });
 }

 render() {
   const { isGood } = this.state;
   return (
    ...
    <TouchableOpacity
      onPress{() => 
        this.setState({ 
          isGood: !isGood,
        });
        this.someAction(...);  <--いいねするaction 
      } 
    >
     ...     <--- いいねボタン(ハート)
    </TouchableOpacity>
    ...
   )
 }

こんな感じ。
わざわざ、storeで管理しているものをいちいちsetStateしているのはいいねして返ってくるまで時間がかかって、色が変わるのがラグく感じてしまうのをやめたかった。
昔のairbnbのいいねはこのタイプだった。
ツイッターはすぐ色変わる。

つまり、onPress内でまず、stateを更新してすぐ色変え、api通信から戻ってきた結果をcomponentWillReceiveProps内でsetStateしている、ということ。
onPress内でsetStateしないバージョンが上のgif(さっき載せたやつと同じ)
setStateすると下のようになる

demo_181220.gif

demo_181220_1.gif

これで一応やりたいことはできている。
ちなみにcomponentWillUpdateの中身でsetStateするのはメモリ食いつぶすので禁止されているのでここでは使えない。

getDerivedStateFromPropsを使う

componentWillReceiveを使っていたところを書き換えた。

 

good.js
 // componentWillReceiveProps(nextProps) {
 // const { isGood } = nextProps;
 //  this.setState({
 //   isGood,  <--- いいねした時apiからレスポンス返ってきたらsetStateする
 //  });
 //}

 static getDerivedStateFromProps(nextProps, prevState) {
   const { isGood } = nextProps;
   if (prevState.isGood !== isGood) { <--nextPropsが更新されたら
     return { isGood: isGood };
   }
   return {};
 }

nextPropsには新しいprops, prevStateには今のstateが自動的に入る。
nextPropsが更新されたらstateを書き換える。
これで全く同じことが実現できた。

getDerivedStateFromPropsはstatic メソッドでこれを実行することは
componentDidUpdate + setStateするのと同じことらしい。

【追記】reduxを使っていればgetDerivedStateFromPropsは不要

コメントをいただいて気づいた。
上で実現したかったことはreduxを使い、変数isGoodをstoreで管理していれば
getDerivedStateFromPropsは不要になる。
一例を書けば、以下のようになる。

good.js
import React from 'react';
...

type Props = {
...
}

const Good = (props: Props) => {
  const { isGood } = props;
  return (
    <TouchableOpacity
      onPress{() => 
        this.setIsGood(!isGood)         <--これがないときはツイッターみたいにすぐ色変わらない
        this.updateIsGood(!isGood);  <--いいねするaction 
      } 
    >
     ...     <--- いいねボタン(ハート)
    </TouchableOpacity>
    ...
   )
 }
actions/index.js
  export const setIsGood = isGood => ({
    type: SET_IS_GOOD,
    payload: isGood,
  });

   export const updateIsGood = isGood => ({
     postでapiにリクエスト飛ばす処理
      .then((res) => {    <-- resに いいねされているかに対応する true or falseが入っているとする
        dispatch(setIsGood(res));  <-- storeに値をsetする
      .catch((err) => {
        dispatch(setIsGood(!isGood)) <-- レスポンスエラーの時storeに変更前の値を入れる
      }
  });
reducers/index.js
const initialState = {
  isGood: false,
};

export function isGoodState(state = initialState, action) {
  switch (action.type) {
    case SET_IS_GOOD:
      return {
        ...state,
        isGood: action.payload,
      };
     default:
       return state;
  }
}

...

const rootReducers = combineReducers({
  isGoodState,
  ...
})

export default rootReducers;

これならgetDerivedStateFromPropsはいらないし、
pure functionで書くことができる

Umibows
ReactとかNodeとかRailsとかPythonとか
Why not register and get more from Qiita?
  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