リアクト状態に深くネストされたオブジェクト/変数を変更するには、一般的に3つの方法が使用されます。バニラはJavaScriptのObject.assign
、不変性ヘルパーとcloneDeep
からLodash。
これを実現する他のあまり人気のないサードパーティのライブラリもたくさんありますが、この回答では、これら3つのオプションのみを取り上げます。また、配列の拡散など、追加のバニラJavaScriptメソッドがいくつか存在しますが(たとえば、@ mpenの回答を参照)、それらは非常に直感的でなく、使いやすく、すべての状態操作状況を処理できます。
回答へのトップ投票コメントで無数の時間が指摘されたように、その作者は状態の直接的な変異を提案しています:ただそれをしないでください。これはユビキタスなReactアンチパターンであり、必然的に望ましくない結果を招きます。正しい方法を学ぶ。
広く使用されている3つの方法を比較してみましょう。
この状態オブジェクト構造を考えると:
state = {
outer: {
inner: 'initial value'
}
}
以下のメソッドを使用してinner
、残りの状態に影響を与えることなく、最も内側のフィールドの値を更新できます。
1. Vanilla JavaScriptのObject.assign
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the shallow copying:', outer.inner) // initial value
const newOuter = Object.assign({}, outer, { inner: 'updated value' })
console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<main id="react"></main>
Object.assign はプロパティ値をコピーするだけなので、ディープクローニングを実行しないことに注意してください。このため、Object.assign が行うことは、浅いコピーと呼ばれます(コメントを参照)。
これが機能するためには、プリミティブ型(outer.inner
)、つまり文字列、数値、ブール値のプロパティのみを操作する必要があります。
この例ではconst newOuter...
、を使用して新しい定数()Object.assign
を作成します。これにより、空のオブジェクト({}
)が作成され、outer
オブジェクト({ inner: 'initial value' }
)がそのオブジェクトにコピーされ、その上に別のオブジェクトがコピーさ{ inner: 'updated value' }
れます。
このようにして、最後に、新しく作成されたnewOuter
定数は{ inner: 'updated value' }
、inner
プロパティがオーバーライドされてからの値を保持します。これnewOuter
は状態のオブジェクトにリンクされていない真新しいオブジェクトなので、必要に応じて変更することができ、状態を維持し、更新するコマンドが実行されるまで状態は変化しません。
最後の部分は、setOuter()
setter を使用outer
して状態の元のnewOuter
オブジェクトを新しく作成されたオブジェクトに置き換えることです(値のみが変更され、プロパティ名outer
は変更されません)。
ここで、のようなより深い状態があると想像してくださいstate = { outer: { inner: { innerMost: 'initial value' } } }
。newOuter
オブジェクトを作成してouter
、状態のコンテンツを設定Object.assign
することもできますが、ネストが深すぎるため、innerMost
の値をこの新しく作成されたnewOuter
オブジェクトにコピーできませんinnerMost
。
inner
上記の例のように、引き続きをコピーすることもできますが、これはオブジェクトであり、プリミティブではないので、からの参照newOuter.inner
がouter.inner
代わりにコピーされnewOuter
ます。つまり、状態のオブジェクトに直接関連付けられているローカルオブジェクトになります。
つまり、この場合、ローカルに作成された変更newOuter.inner
はouter.inner
オブジェクト(状態)に直接影響します。なぜなら、それらは実際には同じもの(コンピューターのメモリ内)になっているためです。
Object.assign
したがって、プリミティブ型の値を保持する最も内側のメンバーを持つ比較的単純な1レベルの深い状態構造がある場合にのみ機能します。
更新する必要があるより深いオブジェクト(第2レベル以上)がある場合は、を使用しないでくださいObject.assign
。状態を直接変更するリスクがあります。
2. LodashのcloneDeep
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the deep cloning:', outer.inner) // initial value
const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
newOuter.inner = 'updated value'
console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<main id="react"></main>
LodashのcloneDeepの方がはるかに使いやすいです。これはディープクローニングを実行するので、内部にマルチレベルのオブジェクトまたは配列を持つかなり複雑な状態がある場合は、強力なオプションです。ただ、cloneDeep()
トップレベルの状態の性質、あなたはどのような方法でクローン化された部分を変異させてください、そしてsetOuter()
それがバック状態に。
3. 不変性ヘルパー
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
const update = immutabilityHelper
console.log('Before the deep cloning and updating:', outer.inner) // initial value
const newOuter = update(outer, { inner: { $set: 'updated value' } })
console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper@3.0.0"></script>
<main id="react"></main>
immutability-helper
全く新しいレベルにそれを取り、それについての涼しい事は、それができるだけでなく、ある$set
状態の項目に値が、また$push
、$splice
、$merge
(など)それら。以下は利用可能なコマンドのリストです。
サイドノート
ここでも、深くネストされた()ではなく、状態オブジェクトsetOuter
の第1レベルのプロパティのみを変更することに注意しouter
てください(これらの例ではouter.inner
)。別の方法で動作した場合、この質問は存在しません。
どちらがあなたのプロジェクトに適していますか?
外部の依存関係が不要または使用できず、単純な状態構造の場合は、に固執しObject.assign
ます。
巨大な状態や複雑な状態を操作する場合cloneDeep
は、Lodash が賢明な選択です。
高度な機能が必要な場合、つまり状態構造が複雑で、あらゆる種類の操作を実行する必要がある場合は、を試してくださいimmutability-helper
。これは、状態操作に使用できる非常に高度なツールです。
...または、本当にこれを行う必要がありますか?
Reactの状態で複雑なデータを保持している場合は、他の方法でデータを処理することを検討するのがよいでしょう。Reactコンポーネントで複雑な状態オブジェクトを正しく設定することは簡単な操作ではないので、さまざまなアプローチについて考えることを強くお勧めします。
おそらく、複雑なデータをReduxストアに保管し、レデューサーやsagasを使用してそこに設定し、セレクターを使用してアクセスすることをお勧めします。