Vueやreactでデータが反映されない!?なぜ?? 問題を再現して解決方法がわかるようになる
開発チームの下田です。
ラクーングループのサービスのフロントはreactやVue.jsに順次更新していっています。その中で様々な問題が発生しますが、
その中でも、私が最も多く受ける相談が「値を変更したはずなのに、画面に反映されない!」というものです。
今回は反映されないパターンをreact、Vue.jsそれぞれでいくつか再現して、なぜ反映されないのか解き明かしていきます。
準備 サンドボックスであそんでみる
react
reactの公式ドキュメントにあるサンドボックスを使用します。
world
を別の単語に書き換えたりして、変更がすぐ反映されることを確認してみてください。
Vue.js
Vue.js SFC Playgroundを使用します。
こちらもtemplateを書き換えるなど、試してみましょう
反映されないパターン1 リアクティブな変数として定義していない
変数の値を変更したとき、画面に即反映させるようにすることをリアクティブにすると呼びます。値をリアクティブにしたい場合は、そのライブラリの規則に従った書き方をする必要があります。
この研修では「Hello, world」と書いてあるh1タグをクリックすると、なんらか文字が変更されるものを作って試していきます。
react
こんな書き方をすると、onClickでnumの値が変更されるのに、画面には変更されません。
function Greeting({ name }) {
let num = 1;
return <h1 onClick={() => num = num + 1}>Hello, {name} {num}</h1>;
}
export default function App() {
return <Greeting name="world" />
}
consoleにログを出力して、クリックイベントが正しく動作していることを確認してみましょう。
function Greeting({ name }) {
let num = 1;
return <h1 onClick={() => console.log(num = num + 1)}>Hello, {name} {num}</h1>;
/** ↑ここに追加 **/
}
export default function App() {
return <Greeting name="world" />
}
みなさんご存知だと思いますが、useStateを使っていないためです。
import { useState } from 'react';
function Greeting({ name }) {
let [num, setNum] = useState(1);
return <h1 onClick={() => setNum(num + 1)}>Hello, {name} {num}</h1>;
}
export default function App() {
return <Greeting name="world" />
}
Greeting
functionでログを出してみると、より理解が深まります。
useStateフックを使用しない場合、Greeting
functionは初回のみ呼び出されますが、クリックしても呼び出されません。
function Greeting({ name }) {
let num = 1;
console.log('Greeting', num); // これを追加
return <h1 onClick={() => console.log(num = num + 1)}>Hello, {name} {num}</h1>;
/** ↑setNumを呼んでいないものに戻した **/
}
useStateフックのset functionを呼んだとき、Greeting functionが実行されます。
function Greeting({ name }) {
let [num, setNum] = useState(1);
console.log('Greeting', num);
return <h1 onClick={() => setNum(num + 1)}>Hello, {name} {num}</h1>;
/** ↑setNumを呼んでみる **/
}
たとえ描画に使用していないset
functionでも、違う値を設定するとGreeting
functionは呼び出されます。
function Greeting({ name }) {
let [kankeinai, setKankeinai] = useState(1);
let num = 1; // 実行されるたびに1に戻る
console.log('Greeting', num);
return <h1 onClick={() => { setKankeinai(kankeinai + 1); num++}}>Hello, {name} {num}</h1>;
/** ↑setKankeinaiを呼んでみる。 ↑numの値を+1している **/
}
- ライブラリに変更を通知するfunctionを呼ぶ
- コンポーネントを作るfunctionが呼び出される
- 画面に反映される
という流れです。
Vue.js
Vue.jsでも同様です。
クリックイベントは正しく動作していますが、コンポーネントのupdateはされません。
<script setup>
import { ref, onUpdated } from 'vue'
const msg = ref('world')
let num = 1;
onUpdated(() => console.log('Updated', num));
</script>
<template>
<h1 @click="() => console.log(++num)">Hello, {{ msg }} {{ num }}</h1>
</template>
refを使用して、Vue.jsに変更を通知するように変更すると、反映されるようになります。
<script setup>
import { ref, onUpdated } from 'vue'
const msg = ref('world')
const num = ref(1); // refで囲む
onUpdated(() => console.log('Updated', num));
</script>
<template>
<h1 @click="() => console.log(++num)">Hello, {{ msg }} {{ num }}</h1>
</template>
Vue.jsがややこしいポイントは、変な書き方をしても動いてしまうときと、動かない場合があります。
numとは関係ないmsgを色んな値に変更してもUpdatedが動くようになります。
<script setup>
import { ref, onUpdated } from 'vue'
const msg = ref('world')
let num = 1; // numはrefで囲んでいない
onUpdated(() => console.log('Updated', num));
</script>
<template>
<h1 @click="() => {num++; msg = '';msg = 'world'}">Hello, {{ msg }} {{ num }}</h1>
<!-- ↑値を変更したいので、空文字にしてから戻す -->
</template>
ただし、値が変わらないときはUpdatedは動きません。
<script setup>
import { ref, onUpdated } from 'vue'
const msg = ref('world')
let num = 1;
</script>
onUpdated(() => console.log('Updated', num));
<template>
<h1 @click="() => {console.log(++num); msg = 'world'}">Hello, {{ msg }} {{ num }}</h1>
</template>
リアクティブな変数が変更されたときに、リアクティブではない変数も含めて再レンダリングされます。全然関係ない変数の値が変わったときだけ反映される状態で、「たまに反映されない場合があるけど何・・?」みたいな感じになっています。Vueでリアクティブになっていない場合は、ログに出してみて型を確認すると、Proxy等の元の型になっているかどうかを確認しましょう。
reactでもリアクティブではない変数を反映させる
reactでも、同じように試すことはできます。
import { useState } from 'react';
let num = 1;
function Greeting({ name }) {
let [kankeinai, setKankeinai] = useState(1);
console.log('Greeting', num);
return <h1 onClick={() => { setKankeinai(kankeinai + 1); num++}}>Hello, {name} {num}</h1>;
}
export default function App() {
return <Greeting name="world" />
}
反映されないパターン2 objectの中身だけ書き換えてしまう
reactではuseStateフックのset function、Vue.jsではrefの値を書き換えたときに、コンポーネントの再描写が行われました。
変数をreactやVueの管理下に置いておいても、迂回してオブジェクトの中身だけ書き換えてしまうと、やはり反映されません。
次のコードは、clickしたときにclicked!!
と表示させたいが表示されないコードです。
react
値は管理されていますが、画面にはレンダリングされません。
import { useState } from 'react';
function Greeting() {
const [obj, setObj] = useState({body: 'Hello World!'});
console.log('Greeting', obj.body);
return <h1 onClick={() => { console.log(obj.body); obj.body = 'clicked!!'; }}>{obj.body}</h1>;
}
export default function App() {
return <Greeting />
}
さらに同じオブジェクトをsetし直したとしても、反映されません。
useStateでsetする前のものと後のものが同じ場合、変更が通知されません。
import { useState } from 'react';
function Greeting() {
const [obj, setObj] = useState({body: 'Hello World!'});
console.log('Greeting', obj.body);
return <h1 onClick={() => { console.log(obj.body); obj.body = 'clicked!!'; setObj(obj) }}>{obj.body}</h1>;
}
export default function App() {
return <Greeting />
}
- ライブラリに変更を通知するfunctionを呼ぶ → ここで変更していないことになる
- コンポーネントを作るfunctionが呼び出される
- 画面に反映される
つまり変更したことにしてあげなければなりません。
import { useState } from 'react';
function Greeting() {
const [obj, setObj] = useState({body: 'Hello World!'});
console.log('Greeting', obj.body);
return <h1 onClick={() => { console.log(obj.body); setObj({...obj, body: 'clicked!!'}) }}>{obj.body}</h1>;
}
export default function App() {
return <Greeting />
}
余談
set
functionで反映させたい値を設定しなくても、関係ない値を変更しライブラリに通知すると、画面に反映されます。
import { useState } from 'react';
function Greeting() {
const [obj, setObj] = useState({body: 'Hello World!'});
const [hoge, setHoge] = useState(null);
console.log('Greeting', obj.body);
return <h1 onClick={() => { console.log(obj.body); obj.body = 'clicked!!'; setHoge(obj.body) }}>{obj.body}</h1>;
}
export default function App() {
return <Greeting />
}
Vue.js
<script setup>
import { ref } from 'vue'
const obj = {body: 'Hello World!'};
const msg = ref(obj);
</script>
<template>
<h1 @click="obj.body = 'clicked!!'; console.log(msg, obj)">{{ msg.body }}</h1>
</template>
Vueもオブジェクトの中身を直接書き換えると、変更が通知されません。
msgではなくobjを書き換えてしまっていること、元になっているmsgも、refがついてるobjも両方値が変わってることに注目してみてください。
解消するには、refで囲んでいる方から呼んであげると解決します。
refで囲むと、そのオブジェクトを書き換えると変更が通知されるようになります。
<script setup>
import { ref } from 'vue'
const obj = {body: 'Hello World!'};
const msg = ref(obj);
</script>
<template>
<h1 @click="msg.body = 'clicked!!'; console.log(msg, obj)">{{ msg.body }}</h1>
</template>
まとめ
Vueもreactも、この3ステップで変数が画面に反映されます。
- ライブラリに変更を通知するfunctionを呼ぶ
- コンポーネントを作るfunctionが呼び出される
- 画面に反映される
1の変更を通知を行うためには、Vueではrefで囲んだ変数を変更すること、reactではuseStateなどのhookを呼ぶことで通知できます。通知するだけではなく、変更前と変更後が変わっていることも重要です。
2は勝手に行われますが、functionにconsole.logを仕込むと反映されていないときに呼び出されているか確認できます。
これらの基本を抑えておくと、なぜ反映されていないか調査し、どこでライブラリに通知するべきか調整でき、解決できるようになると思います。
さて、ラクーンホールディングスではエンジニア・デザイナー・HTMLコーダーを大募集中です!
興味を持っていただいた方は是非、お話ししましょう!