React入門初心者マンとして引き続きエクセル的なモノを作るチュートリアルをやっています。
今回テーブルをソートしようとしてハマったのでメモ。
react: ^16.8.3 react-dom: ^16.8.3
リストのような配列から作られるエレメントには key
を指定する
V-DOMはDOMと同じツリー構造になっていて、構造を比較して違いがあるとその箇所を実際のDOMに反映させる仕組み。
リスト構造は変更があっても、リスト内のどこに変更があったのか判断できない。
key
が設定されていると、key
を頼りにどこが変更されたのかをReactが判断できるので、更新の反映が最小限になるって事っぽい。
配列を.map
して作成するようなエレメントはkey
を付けないとwarningになる
e. g.
import React, {Component} from 'react'; import ReactDOM from 'react-dom'; const data = [ ['0315', '星宮いちご'], ['0131', '霧矢あおい'], ['0803', '紫吹蘭'], ]; class List extends Component { render() { const list = this.props.data.map((row, i) => { return(<li>ID[ {row[0]} ]: {row[1]}</li>); }); return (<ul>{list}</ul>); } } ReactDOM.render( <List data={data} />, document.getElementById('app') );
key
を指定するようにワーニングが出る
Warning: Each child in a list should have a unique "key" prop.
map()
で作られるリスト要素にkey
属性を付ければOK
class List extends Component { render() { const list = this.props.data.map((row, i) => { return(<li key={i}>ID[ {row[0]} ]: {row[1]}</li>); }); return (<ul>{list}</ul>); } }
コンポーネントな時はmap()
で内にある側だけにkey
を設定すれば良い
function ListItem(props) { const row = props.data; // こちらに key を設定する必要はない return (<li>ID[ {row[0]} ]: {row[1]}</li>); } class List extends Component { render() { const list = this.props.data.map((row, i) => { return(<ListItem key={i} data={row} />); }); return (<ul>{list}</ul>); } }
map()
で作っている所にkey
が無ければワーニング
function ListItem(props) { const row = props.data; return (<li key={props.idx}>ID[ {row[0]} ]: {row[1]}</li>); } class List extends Component { render() { const list = this.props.data.map((row, i) => { // ここに key がないとダメ return(<ListItem idx={i} data={row} />); }); return (<ul>{list}</ul>); } }
👉 Warning: Each child in a list should have a unique "key" prop.
key
は変わらないものを指定する
ソートなどで並び順が変わるようリストの場合、key
をindex
などにしていると変更内容が render されない場合がある
sample
See the Pen React List Element Sort TEST by KIKIKI (@chaika-design) on CodePen.
🙅♂️keyの指定がなかったり、indexがしていされているとソートされないパターン
リスト内のコンポーネントがstate
を持つ場合、リストのkey
の指定がなかったり、key
がindexになっているとソートでデータの並び順が変わっても正しくrenderされない
const data = [ ['0315', '星宮いちご'], ['0131', '霧矢あおい'], ['0803', '紫吹蘭'], ]; // state を持つコンポーネント class ListContentWithState extends React.Component { constructor(props) { super(props); this.state = { data: this.props.data, } } render() { const data = this.state.data; return (<span>ID[ {data[0]} ]: {data[1]}</span>); } } class List extends React.Component { constructor(props) { super(props); this.state = { data: this.props.initData, descending: false, } } _onSort() { const data = this.state.data.slice(); const descending = !this.state.descending; console.log('> sort', (descending? 'DESC':'ASC')); data.sort((a, b) => { if( descending ) { return a[0] < b[0]? 1:-1; } else { return a[0] > b[0]? 1:-1; } }); this.setState({ data: data, descending: descending, }); } _onReset() { console.log('> reset'); this.setState({ data: this.props.initData, descending: false, }); } render() { console.log('> render', this.state.data); const list = this.state.data.map((row, i) => { // keyが無かったり、indexのようにソート時に変わってしまうものだと、 // stateが変更されてもrenderされない return(<li key={i}><ListContentWithState data={row} /></li>); }); return ( <div> <button onClick={(e)=>this._onSort()}>SORT</button> <button onClick={(e)=>this._onReset()}>RESET</button> <ul>{list}</ul> </div> ); } } ReactDOM.render( <List initData={data} />, document.getElementById('app') );
👇 key
の指定をソートしても変わらないものにすればOK
class List extends React.Component { // ... render() { const list = this.state.data.map((row, i) => { // ソートしても変わらないkeyを指定すればOK const listID = row[0]; return(<li key={listID}><ListContentWithState data={row} /></li>); }); // ... } }
なんとなくですが、リスト内の子コンポーネント(ListContentWithState
)がstate
を持っている場合、子コンポーネント作成時にまずkey
が渡されて、そのkey
に該当するデータからstate
を返しているから、最初の並びの順番で子コンポーネントが返され親コンポーネント(List
)のstate.data
の順番は変更されているのに、render
はその順番で出力されないい。という感じなのではないかという印象です。
なので、子コンポーネントがstate
を持たなければ、親コンポーネントのリストのkey
は指定がなくても、ソート時に変更されてしまうindex
でもソートされた内容の通りにrenderされます。が、恐らくkey
の指定がありReactが変更箇所だけをうまく変更してくれるより処理は重いのではないかと思います。
👇
🙆♂️keyの指定がなかったり、indexで指定されていてもソートされるパターン
1. リストエレメントの内容が直接書かれている場合
class List extends React.Component { constructor(props) { super(props); this.state = { data: this.props.initData, descending: false, } } _onSort() { const data = this.state.data.slice(); const descending = !this.state.descending; console.log('> sort', (descending? 'DESC':'ASC')); data.sort((a, b) => { if( descending ) { return a[0] < b[0]? 1:-1; } else { return a[0] > b[0]? 1:-1; } }); this.setState({ data: data, descending: descending, }); } _onReset() { console.log('> reset'); this.setState({ data: this.props.initData, descending: false, }); } render() { const list = this.state.data.map((row, i) => { // リストエレメントの key が index / 無し でもソートは反映され描画される return(<li key={i}>ID[ {row[0]} ]: {row[1]}</li>); }); return ( <div> <button onClick={(e)=>this._onSort()}>SORT</button> <button onClick={(e)=>this._onReset()}>RESET</button> <ul>{list}</ul> </div> ); } } ReactDOM.render( <List initData={data} />, document.getElementById('app') );
2. リストエレメント内にコンポーネントを持っているがstate
を使っていない場合
リスト内にあるコンポーネントがstate
を持っていないならkey
は無くてもindex
を指定していてもソートがrenderされる
// state を持たないコンポーネント function ListContent(props) { const data = props.data; return (<span>ID[ {data[0]} ]: {data[1]}</span>); } class List extends React.Component { constructor(props) { super(props); this.state = { data: this.props.initData, descending: false, } } _onSort() { const data = this.state.data.slice(); const descending = !this.state.descending; console.log('> sort', (descending? 'DESC':'ASC')); data.sort((a, b) => { if( descending ) { return a[0] < b[0]? 1:-1; } else { return a[0] > b[0]? 1:-1; } }); this.setState({ data: data, descending: descending, }); } _onReset() { console.log('> reset'); this.setState({ data: this.props.initData, descending: false, }); } render() { const list = this.state.data.map((row, i) => { // state を持たないコンポーネントなら key が index でもOK return(<li key={i}><ListContent data={row} /></li>); }); return ( <div> <button onClick={(e)=>this._onSort()}>SORT</button> <button onClick={(e)=>this._onReset()}>RESET</button> <ul>{list}</ul> </div> ); } } ReactDOM.render( <List initData={data} />, document.getElementById('app') );
key
map()
で作られるようなリストエレメントにはkey
を設定したほうがパフォーマンスが良いkey
はリスト内でユニークなものにする
key
は同じ反復するエレメント内だけでユニークであればよく、globalとしてユニークである必要はないkey
は React自身のツリー構造のチェックに使われる- 明示的に
key
を指定しない場合はindexがデフォルトとして使用される key
はprops
として渡されない- 並び順が変わるようなリストの場合
index
をkey
にするとパフォーマンスが悪く、stateに問題がでる場合もあるので良くない
まとめ
Reactは頑張って英語の本家ドキュメントを読もう! (変化が早いのでそれが確実な近道...)
[参考]
React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)
- 作者: 穴井宏幸,石井直矢,柴田和祈,三宮肇
- 出版社/メーカー: 翔泳社
- 発売日: 2018/02/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る