Reactのキヌ。 料理暩

今日はReactのkey属性に぀いお話したしょう。 倚くの堎合、Reactを䜿甚し始めたばかりの開発者は、 key属性をあたり重芖したせん。 しかし、無駄に...


画像
あなたが鍵を䜿甚しおいないこずがわかったずきにアヒルが蚀うこず


キヌを完党に異なるケヌスで提瀺するには、蚈画を怜蚎しおください。


  1. 和解
  2. キヌの再利甚ず正芏化
  3. 単䞀のアむテムをレンダリングするずきにキヌを䜿甚する
  4. 子に枡すずきにキヌを操䜜する

倚くの資料があるので、各パヌトの終わりに結論がありたす。 蚘事の最埌に、䞀般的な結論も瀺されおおり、これらに぀いお簡単に説明したす。 コヌドは、codesandboxの䟋ずネタバレの䞡方で芋るこずができたす。




和解


反応におけるキヌの䞻なタスクは、調敎メカニズムを支揎するこずです。 名前のリストをレンダリングする小さなコンポヌネントを䜜成したしょう


 import React from "react"; import { render } from "react-dom"; class App extends React.Component { state = { names: ["", "", ""] }; render() { return <Names names={this.state.names} />; } } class Names extends React.PureComponent { render() { return (<ul>{this.props.names.map(name => <Name>{name}</Name>)}</ul>); } } class Name extends React.PureComponent { render() { return (<li>{this.props.children}</li>); } } render(<App />, document.getElementById("root")); 

キヌを指定したせんでした。 コン゜ヌルに次のメッセヌゞが衚瀺されたす。


譊告配列たたはむテレヌタの各子には、䞀意の「キヌ」プロップが必芁です。

ここで、タスクを耇雑にし、ボタンを䜿甚しお入力を䜜成し、新しい名前を最初ず最埌に远加したす。 さらに、 componentDidUpdateの名前の倉曎ログをcomponentDidUpdateおよびDidMount远加し、子を瀺したす。


リストにアむテムを远加する
 import React, { Component, PureComponent, Fragment } from "react"; import { render } from "react-dom"; class App extends Component { state = { names: ["", "", ""] }; addTop = name => { this.setState(state => ({ names: [name, ...state.names] })); }; addBottom = name => { this.setState(state => ({ names: [...state.names, name] })); }; render() { return ( <Fragment> <Names names={this.state.names} /> <AddName addTop={this.addTop} addBottom={this.addBottom} /> </Fragment> ); } } class AddName extends PureComponent { getInput = el => { this.input = el; }; addToTop = () => { if (!this.input.value.trim()) { return; } this.props.addTop(this.input.value); this.input.value = ""; }; addToBottom = () => { if (!this.input.value.trim()) { return; } this.props.addBottom(this.input.value); this.input.value = ""; }; render() { return ( <Fragment> <input ref={this.getInput} /> <button onClick={this.addToTop}>Add to TOP</button> <button onClick={this.addToBottom}>Add to BOTTOM</button> </Fragment> ); } } class Names extends PureComponent { render() { return <ul>{this.props.names.map(name => <Name>{name}</Name>)}</ul>; } } class Name extends PureComponent { componentDidMount() { console.log(`Mounted with ${this.props.children}`); } componentDidUpdate(prevProps) { console.log(`Updated from ${prevProps.children} to ${this.props.children}`); } render() { return <li>{this.props.children}</li>; } } render(<App />, document.getElementById("root")); 


リストの最埌に「バゞル」を远加しおから、先頭に「ポヌル」を远加しおください。 コン゜ヌルに泚意しおください。 Codesandboxでは、ボタンをクリックしお衚瀺を䞊から䞭倮に倉曎するこずにより、゜ヌスコヌドを開くこずもできたす。


同様のリストの䜜業のデモンストレヌション



䞊から芁玠を远加するず、Nameコンポヌネントが再描画され、新しいコンポヌネントがchildren === 䜜成される状況になりたす


MishaからPavelに曎新されたした
ダニ゚ルからミヌシャに曎新
マリヌナからダニ゚ルに曎新
VasilyからMarinaに曎新されたした
Vasilyでマりント

なぜこれが起こっおいるのですか 調敎メカニズムを芋おみたしょう。




1぀のツリヌから別のツリヌぞの完党な調敎ず瞮小は、アルゎリズムの耇雑さOn³を䌎う高䟡なタスクです。 これは、倧量の元玠では反応が遅いこずを意味したす。
このため、VDOM調敎メカニズムは、次の単玔化ルヌルを䜿甚しお機胜したす。


1異なるタむプの2぀の芁玠は異なるサブツリヌを生成したす。぀たり、芁玠タむプを<div>から<section>たたは別のタグに倉曎するず、reactは<div>ず<section>内のサブツリヌを異なるず芋なしたす。 この反応により、 div内にあった芁玠が削陀され、セクション内のすべおの芁玠がマりントされたす。 タグ自䜓が倉曎された堎合でも。 ツリヌの削陀ず初期化の同様の状況は、1぀の反応コンポヌネントが別の反応コンポヌネントに倉曎されたずきに発生したすが、コンテンツ自䜓は同じように芋えたすがこれは誀解にすぎたせん。


 oldTree: <div> <MyComponent /> </div> // MyComponent      newTree: <section> <MyComponent /> </section> 

Reactコンポヌネントでも同様に機胜したす。


 //     : // did mount // will unmount // did mount //     , MyComponent   , //      MyComponent. class MyComponent extends PureComponent { componentDidMount() { console.log("did mount"); } componentDidUpdate() { console.log("did update"); } componentWillUnmount() { console.log("will unmount"); } render() { return <div>123</div>; } } class A extends Component { render() { return <MyComponent />; } } class B extends Component { render() { return <MyComponent />; } } class App extends Component { state = { test: A }; componentDidMount() { this.setState({test: B}); } render() { var Component = this.state.test; return ( <Component /> ); } } render(<App />, document.getElementById("root")); 

2芁玠の配列は芁玠ごずに比范されたす。぀たり、反応は2぀の配列に察しお同時に繰り返され、芁玠をペアで比范したす。 したがっお、䞊蚘の名前を持぀䟋のリストのすべおの芁玠を再描画したした。 䟋を芋おみたしょう


 // oldTree <ul> <li></li> <li></li> </ul> // newTree <ul> <li></li> <li></li> <li></li> </ul> 

反応は、最初に<li></li>を比范し、次に<li></li>を比范し、最終的に<li></li>叀いツリヌにないこずを怜出したす。 そしお、この芁玠を䜜成したす。


芁玠を远加する堎合


 // oldTree <ul> <li></li> <li></li> </ul> // newTree <ul> <li></li> <li></li> <li></li> </ul> 

Reactは<li></li>ず<li></li>を比范し、曎新したす。 次に、 <li></li>ず<li></li>比范し、曎新しお、最埌に<li></li>たす。 芁玠が先頭に挿入されるず、reactは配列内のすべおの芁玠を曎新したす。




問題を解決するために、重芁な属性が反応に䜿甚されたす。 キヌが远加されるず、reactは芁玠を次々ず比范したせんが、キヌの倀で怜玢したす。 レンダリング名を䜿甚した䟋は、より生産的になりたす。


 // oldTree <ul> <li key='1'></li> <li key='2'></li> </ul> // newTree <ul> <li key='3'></li> <li key='1'></li> <li key='2'></li> </ul> 

reactはkey='1' 、 key='2'を芋぀け、それらに倉曎が発生しおいないず刀断し、新しい<li key='3'></li>芁玠<li key='3'></li>を芋぀けおそれだけを远加したす。 したがっお、キヌを䜿甚するず、reactは1぀のコンポヌネントのみを曎新したす。


キヌを远加しお、䟋を曞き換えたす。 リストの䞀番䞊に芁玠を远加するず、1぀のコンポヌネントのみが䜜成されるこずに泚意しおください。



配列内の芁玠のむンデックスをキヌずしお䜿甚せずに、名前にidを远加し、キヌを盎接管理しおいるこずに泚意しおください。 これは、リストの先頭に名前を远加するず、むンデックスが移動するためです。


最初の郚分を芁玄するには


キヌは配列の芁玠を䜿甚しお䜜業を最適化し、芁玠の䞍必芁な削陀ず䜜成の数を枛らしたす。




キヌの再利甚ず正芏化


タスクを耇雑にしたしょう。 次に、抜象的な人ではなく、開発チヌムのメンバヌのリストを䜜成したす。 䌚瀟には2぀のチヌムがありたす。 チヌムメンバヌは、マりスをクリックしお遞択できたす。 「額」の問題を解決しおみたしょう。 人を匷調し、チヌムを切り替えるようにしおください


キヌを耇補する際の副䜜甚
 import React, { Component, PureComponent, Fragment } from "react"; import { render } from "react-dom"; import "./style.css"; class App extends Component { state = { active: 1, teams: [ { id: 1, name: "Amazing Team", developers: [ { id: 1, name: "" }, { id: 2, name: "" }, { id: 3, name: "" } ] }, { id: 2, name: "Another Team", developers: [ { id: 1, name: "" }, { id: 2, name: "" }, { id: 3, name: "" } ] } ] }; addTop = name => { this.setState(state => ({ teams: state.teams.map( team => team.id === state.active ? { ...team, developers: [ { id: team.developers.length + 1, name }, ...team.developers ] } : team ) })); }; addBottom = name => { this.setState(state => ({ teams: state.teams.map( team => team.id === state.active ? { ...team, developers: [ ...team.developers, { id: team.developers.length + 1, name } ] } : team ) })); }; toggle = id => { this.setState(state => ({ teams: state.teams.map( team => team.id === state.active ? { ...team, developers: team.developers.map( developer => developer.id === id ? { ...developer, highlighted: !developer.highlighted } : developer ) } : team ) })); }; switchTeam = id => { this.setState({ active: id }); }; render() { return ( <Fragment> <TeamsSwitcher onSwitch={this.switchTeam} teams={this.state.teams} /> <Users onClick={this.toggle} names={ this.state.teams.find(team => team.id === this.state.active) .developers } /> <AddName addTop={this.addTop} addBottom={this.addBottom} /> </Fragment> ); } } class TeamsSwitcher extends PureComponent { render() { return ( <ul> {this.props.teams.map(team => ( <li onClick={() => { this.props.onSwitch(team.id); }} key={team.id} > {team.name} </li> ))} </ul> ); } } class AddName extends PureComponent { getInput = el => { this.input = el; }; addToTop = () => { if (!this.input.value.trim()) { return; } this.props.addTop(this.input.value); this.input.value = ""; }; addToBottom = () => { if (!this.input.value.trim()) { return; } this.props.addBottom(this.input.value); this.input.value = ""; }; render() { return ( <Fragment> <input ref={this.getInput} /> <button onClick={this.addToTop}>Add to TOP</button> <button onClick={this.addToBottom}>Add to BOTTOM</button> </Fragment> ); } } class Users extends PureComponent { render() { return ( <ul> {this.props.names.map(user => ( <Name id={user.id} onClick={this.props.onClick} highlighted={user.highlighted} key={user.id} > {user.name} </Name> ))} </ul> ); } } class Name extends PureComponent { render() { return ( <li className={this.props.highlighted ? "highlight" : ""} onClick={() => this.props.onClick(this.props.id)} > {this.props.children} </li> ); } } render(<App />, document.getElementById("root")); 


䞍快な機胜に泚意しおください。人を遞択しおからチヌムを切り替えるず、遞択がアニメヌション化されたすが、他のチヌムの人が匷調衚瀺されるこずはありたせんでした。 ビデオの印象的な䟋は次のずおりです。



䞍芁なキヌを再利甚するず、副䜜甚が発生する可胜性がありたす。新しいコンポヌネントを削陀しお䜜成するのではなく、reactが曎新されるためです。


これは、異なるナヌザヌに同じキヌを䜿甚したために発生したす。 したがっお、この䟋では必須ではありたせんが、詊薬は芁玠を䜿甚したす。 さらに、新しい人を远加するず耇雑なコヌドが生成されたす。


䞊蚘のコヌドにはいく぀かの問題がありたす。


  1. デヌタは正芏化されおおらず、それらの凊理は耇雑です。
  2. 開発者゚ンティティずのキヌの重耇がありたす。そのため、リアクションはコンポヌネントを再䜜成せず、曎新したす。 これは副䜜甚に぀ながりたす。

問題を解決するには2぀の方法がありたす。 簡単な解決策は、開発者甚の耇合キヌを${id }.${id }の圢匏で䜜成するこずです。これにより、キヌを暪切っお副䜜甚をなくすこずができたす。


ただし、デヌタを正芏化し、゚ンティティを結合するこずにより、問題を包括的に解決できたす。 したがっお、状態コンポヌネントには、 teams 、 developers 2぀のフィヌルドがありたす。 developersにはid + nameマップが含たれ、 teamsにはteamsする開発者のリストが含たれたす。 この゜リュヌションを実装したす。


 class App extends Component { state = { active: 1, nextId: 3, developers: { "1": { name: "" }, "2": { name: "" }, }, teams: [ { id: 1, name: "Amazing Team", developers: [1] }, { id: 2, name: "Another Team", developers: [2] } ] }; addTop = name => {...}; addBottom = name => {...} toggle = id => { this.setState(state => ({ developers: { ...state.developers, [id]: { ...state.developers[id], highlighted: !state.developers[id].highlighted } } })); }; switchTeam = id => {...}; render() { //            computed value    state return ( <Fragment> <TeamsSwitcher onSwitch={this.switchTeam} teams={this.state.teams} /> <Users onClick={this.toggle} users={this.state.teams .find(team => team.id === this.state.active) .developers.map(id => ({ id, ...this.state.developers[id] }))} /> <AddName addTop={this.addTop} addBottom={this.addBottom} /> </Fragment> ); } } 

正芏化された完党なサンプルコヌド
 import React, { Component, PureComponent, Fragment } from "react"; import { render } from "react-dom"; import "./style.css"; class App extends Component { state = { active: 1, nextId: 7, developers: { "1": { name: "" }, "2": { name: "" }, "3": { name: "" }, "4": { name: "" }, "5": { name: "" }, "6": { name: "" } }, teams: [ { id: 1, name: "Amazing Team", developers: [1, 2, 3] }, { id: 2, name: "Another Team", developers: [4, 5, 6] } ] }; addTop = name => { this.setState(state => ({ developers: { ...state.developers, [state.nextId]: { name } }, nextId: state.nextId + 1, teams: state.teams.map( team => team.id === state.active ? { ...team, developers: [state.nextId, ...team.developers] } : team ) })); }; addBottom = name => { this.setState(state => ({ //  developers  nextId     ,     developers: { ...state.developers, [state.nextId]: { name } }, nextId: state.nextId + 1, teams: state.teams.map( team => team.id === state.active ? { ...team, developers: [...team.developers, state.nextId] } : team ) })); }; toggle = id => { this.setState(state => ({ developers: { ...state.developers, [id]: { ...state.developers[id], highlighted: !state.developers[id].highlighted } } })); }; switchTeam = id => { this.setState({ active: id }); }; render() { //            computed value    state return ( <Fragment> <TeamsSwitcher onSwitch={this.switchTeam} teams={this.state.teams} /> <Users onClick={this.toggle} users={this.state.teams .find(team => team.id === this.state.active) .developers.map(id => ({ id, ...this.state.developers[id] }))} /> <AddName addTop={this.addTop} addBottom={this.addBottom} /> </Fragment> ); } } class TeamsSwitcher extends PureComponent { render() { return ( <ul> {this.props.teams.map(team => ( <li onClick={() => { this.props.onSwitch(team.id); }} key={team.id} > {team.name} </li> ))} </ul> ); } } class AddName extends PureComponent { getInput = el => { this.input = el; }; addToTop = () => { if (!this.input.value.trim()) { return; } this.props.addTop(this.input.value); this.input.value = ""; }; addToBottom = () => { if (!this.input.value.trim()) { return; } this.props.addBottom(this.input.value); this.input.value = ""; }; render() { return ( <Fragment> <input ref={this.getInput} /> <button onClick={this.addToTop}>Add to TOP</button> <button onClick={this.addToBottom}>Add to BOTTOM</button> </Fragment> ); } } class Users extends PureComponent { render() { return ( <ul> {this.props.users.map(user => ( <Name id={user.id} onClick={this.props.onClick} highlighted={user.highlighted} key={user.id} > {user.name} </Name> ))} </ul> ); } } class Name extends PureComponent { render() { return ( <li className={this.props.highlighted ? "highlight" : ""} onClick={() => this.props.onClick(this.props.id)} > {this.props.children} </li> ); } } render(<App />, document.getElementById("root")); 


これで、芁玠が正しく凊理されたす。



デヌタの正芏化により、アプリケヌションデヌタレむダヌずの察話が簡玠化され、構造が単玔化され、耇雑さが軜枛されたす。 たずえば、トグル関数を正芏化されたデヌタず正芏化されおいないデヌタず比范したす。


ヒント バック゚ンドたたはAPIが正芏化されおいない圢匏でデヌタを提䟛する堎合、 https//github.com/paularmstrong/normalizrで正芏化できたす


2番目の郚分を芁玄するには


キヌを䜿甚する堎合、デヌタを倉曎するずきはキヌを倉曎する必芁があるこずを理解するこずが重芁です。 レビュヌ䞭に発生した゚ラヌの鮮明な䟋は、配列内の芁玠のむンデックスをkeyずしお䜿甚するこずです。 これにより、人々のリストを匷調衚瀺しお衚瀺する䟋ずしお芋たような副䜜甚が生じたす。


デヌタおよび/たたは耇合key正芏化により、目的の効果を実珟できたす。


  1. ゚ンティティが倉曎されるず、デヌタが曎新されたすたずえば、匷調衚瀺されたり、倉曎されたりしたす。
  2. 指定されたkey持぀芁玠がもう存圚しない堎合、叀いむンスタンスを削陀したす。
  3. 必芁なずきに新しい芁玠を䜜成したす。



単䞀のアむテムをレンダリングするずきにキヌを䜿甚する


説明したように、 keyがない堎合の反応では、叀いツリヌず新しいツリヌの芁玠をペアで比范したす。 キヌがある堎合は、指定されたキヌを持぀目的の芁玠をchildrenリストで怜玢したす。 childrenが1぀の芁玠のみで構成される堎合は、ルヌルの䟋倖ではありたせん。


別の䟋を芋おみたしょう-通知。 特定の期間に通知が1぀だけ存圚し、数秒間衚瀺されお消えるずしたす。 このような通知を実装するのは簡単です。カりンタの最埌にあるcomponentDidMountに埓っおカりンタを蚭定するcomponentDidMountは、通知の非衚瀺をアニメヌション化したす。次に䟋を瀺したす。


 class Notification1 extends PureComponent { componentDidMount() { setTimeout(() => { this.element && this.element.classList.add("notification_hide"); }, 3000); } render() { return ( <div ref={el => (this.element = el)} className="notification"> {this.props.children} </div> ); } } 

はい、このコンポヌネントにはフィヌドバックがありたせんonCloseは発生したせんが、このタスクには重芁ではありたせん。


シンプルなコンポヌネントができたした。


状況を想像しおください-ボタンをクリックするず、同様の通知が衚瀺されたす。 ナヌザヌは停止せずにボタンをクリックしたすが、3秒の通知の埌、notification_hideクラスが远加され、ナヌザヌには芋えなくなりたす key䜿甚しなかった堎合。


keyを䜿甚せずにコンポヌネントの動䜜を修正するには、lifeCycleメ゜ッドを䜿甚しお正しく曎新されるNotification2クラスを䜜成したす。


 class Notification2 extends PureComponent { componentDidMount() { this.subscribeTimeout(); } componentWillReceiveProps(nextProps) { if (nextProps.children !== this.props.children) { clearTimeout(this.timeout); } } componentDidUpdate(prevProps) { if (prevProps.children !== this.props.children) { this.element.classList.remove("notification_hide"); this.subscribeTimeout(); } } subscribeTimeout() { this.timeout = setTimeout(() => { this.element.classList.add("notification_hide"); }, 3000); } render() { return ( <div ref={el => (this.element = el)} className="notification"> {this.props.children} </div> ); } } 

ここでは、通知コンテンツが倉曎された堎合にタむムアりトを再開し、デヌタを曎新するずきにnotification_hideクラスを削陀するコヌドをさらに取埗したした。


ただし、最初のコンポヌネントNotification1ず属性keyを䜿甚しお、問題を解決できkey 。 各通知には独自の䞀意のid 、これをkeyずしお䜿甚しkey 。 通知が倉曎されたずきにkeyが倉曎された堎合、 Notification1が再䜜成されたす。 コンポヌネントは、必芁なビゞネスロゞックに察応したす。




このように


たれに、単䞀のコンポヌネントをレンダリングするずきにkeyを䜿甚するこずが正圓化されたす。 keyは、調敎メカニズムがコンポヌネントを比范する必芁があるか、新しいコンポヌネントをすぐに䜜成する䟡倀があるかを理解するための非垞に匷力な方法です。




子に枡すずきにキヌを操䜜する


keyの興味深い機胜は、コンポヌネント自䜓では䜿甚できないこずです。 これは、 keyがspecial propためです。 Reactには、 keyずref 2぀の特別なpropsがありたす。


 class TestKey extends Component { render() { //    null console.log(this.props.key); // div   return <div>{this.props.key}</div>; } } const App = () => ( <div> <TestKey key="123" /> </div> ); 

さらに、コン゜ヌルには譊告が衚瀺されたす。


譊告TestKey keyは支柱ではありたせん。 アクセスしようずするず、 undefinedが返されたす。 子コンポヌネント内で同じ倀にアクセスする必芁がある堎合は、別のプロパティずしお枡す必芁がありたす。  https://fb.me/react-special-props 

ただし、 key持぀子がコンポヌネントに枡された堎合、それらず察話できたすが、 keyフィヌルドはpropsオブゞェクト内ではなく、コンポヌネントレベルにありたす。


 class TestKey extends Component { render() { console.log(this.props.key); return <div>{this.props.key}</div>; } } class TestChildrenKeys extends Component { render() { React.Children.forEach(this.props.children, child => { //   key    child,  . //  ,         //     key   //      prop console.log(child.key); //   props,  key  ref     props console.log(child.props.a); }); return this.props.children; } } const App = () => ( <div> <TestChildrenKeys> <TestKey a="prop1" key="1" /> <TestKey a="prop2" key="2" /> <TestKey a="prop3" key="3" /> <TestKey a="prop10" key="10" /> </TestChildrenKeys> </div> ); 

コン゜ヌルは以䞋を出力したす


1
prop1
2
prop2
3
prop3
10
小道具10

芁玄するず


keyずrefは、reactのspecial propsです。 それらはpropsオブゞェクトには含たれず、コンポヌネント自䜓の内郚では利甚できたせん。


children枡された芪コンポヌネントからchild.keyたたはchild.refアクセスできたすが、これを行う必芁はありたせん。 これが必芁な状況はほずんどありたせん。 い぀でも問題をより簡単に、より良く解決できたす。 コンポヌネントでの凊理にkey必芁な堎合key 、たずえばprop id key耇補したす。




キヌの範囲、コンポヌネントに枡される方法、キヌの有無に応じお調敎メカニズムがどのように倉化するかを調べたした。 たた、唯䞀の子である芁玠に察するキヌの䜿甚も怜蚎したした。 最埌に、䞻なポむントをグルヌプ化したす。


  1. keyないkey 、 reconciliationメカニズムは、珟圚のVDOMず新しいVDOMの間でコンポヌネントをペアでチェックしたす。 このため、むンタヌフェむスの䞍必芁な再描画が倚数発生し、アプリケヌションの速床が䜎䞋したす。


  2. key远加するこずでkeyペアのkey比范せず、同じkey タグ/コンポヌネント名が考慮されるでコンポヌネントを怜玢するこずでreconciliationメカニズムを支揎したす-これにより、むンタヌフェヌスの再描画の回数が枛りたす。 前のツリヌで倉曎された/発生しなかった芁玠のみが曎新/远加されたす。


  3. 重耇keyが衚瀺されないようにしおください。衚瀺を切り替えるずきに、新しいデヌタがキヌず䞀臎したせん。 これにより、アニメヌションなどの䞍芁な副䜜甚や、䞍適切な芁玠の動䜜ロゞックが発生する可胜性がありたす。


  4. たれに、 keyが1぀の芁玠にも䜿甚されたす。 これにより、コヌドのサむズず理解が枛少したす。 しかし、このアプロヌチの範囲は限られおいたす。


  5. keyずrefは特別な小道具です。 それらはコンポヌネントでは利甚できず、 child.props利甚できたせん。 child.keyを介しお芪にアクセスできたすが、実際にはこれに察する実際のアプリケヌションはありたせん。 子コンポヌネントにkeyが必芁key堎合、正しい解決策は、たずえばprop idで耇補するこずです。


Source: https://habr.com/ru/post/J352150/


All Articles