案例演示
本节内容根据官方文档的 教程 编写。
在线demo
案例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
| import React from 'react' import ReactDOM from 'react-dom' import './index.css'
function Square (props) { return ( <button className="square" onClick={props.onClick} > {props.value} </button> ) }
class Board extends React.Component { renderSquare (i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ) }
render () { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ) } }
class Game extends React.Component { constructor(props) { super(props) this.state = { history: [ { squares: Array(9).fill(null) } ], stepNumber: 0, xIsNext: true, } }
handleClick (i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1] const squares = current.squares.slice()
if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O' this.setState({ history: history.concat([{ squares }]), stepNumber: history.length, xIsNext: !this.state.xIsNext }) }
jumpTo (step) { this.setState({ stepNumber: step, xIsNext: (step % 2) === 0 }) }
render () { const history = this.state.history const current = history[this.state.stepNumber] const winner = calculateWinner(current.squares)
const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)}>{desc}</button> </li> ) })
let status if (winner) { status = '获胜者: ' + winner; } else { status = '下一步玩家: ' + (this.state.xIsNext ? 'X' : 'O'); }
return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i) => this.handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ) } }
ReactDOM.render( <Game />, document.getElementById('root') )
function calculateWinner (squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ];
for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; }
|
为什么不可变性在 React 中非常重要
案例中,在修改state
时并非直接修改对象属性内的某个直,而是先使用.slice()
方法创建了数组的一个副本。然后在通过setState()
方法,替换掉整个数组。
一般来说,有两种改变数据的方式。第一种方式是直接修改变量的值,第二种方式是使用新的一份数据替换旧数据。
直接修改数据
1 2 3
| var player = {score: 1, name: 'Jeff'}; player.score = 2;
|
新数据替换旧数据
1 2 3 4 5 6 7
| var player = {score: 1, name: 'Jeff'};
var newPlayer = Object.assign({}, player, {score: 2});
|
不直接修改(或改变底层数据)这种方式和前一种方式的结果是一样的,这种方式有以下几点好处:
简化复杂的功能
不可变性使得复杂的特性更容易实现。在后面的章节里,我们会实现一种叫做“时间旅行”的功能。“时间旅行”可以使我们回顾井字棋的历史步骤,并且可以“跳回”之前的步骤。这个功能并不是只有游戏才会用到——撤销和恢复功能在开发中是一个很常见的需求。不直接在数据上修改可以让我们追溯并复用游戏的历史记录。
跟踪数据的改变
如果直接修改数据,那么就很难跟踪到数据的改变。跟踪数据的改变需要可变对象可以与改变之前的版本进行对比,这样整个对象树都需要被遍历一次。
跟踪不可变数据的变化相对来说就容易多了。如果发现对象变成了一个新对象,那么我们就可以说对象发生改变了。
确定在 React 中何时重新渲染
不可变性最主要的优势在于它可以帮助我们在 React 中创建 pure components。我们可以很轻松的确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染。
查阅性能优化章节,以了解更多有关 shouldComponentUpdate()
函数及如何构建 pure components 的内容。