通过官网的九宫格棋子的小例子学习一下react的基础使用,不知道与vue的差别大不大。
组件
react中自定义组件通过继承 React.Component 来实现,通过render方法渲染所希望看到的dom结构,实际上render返回的是一个react元素,使用JSX语法能让我们直接书写dom结构而不需重复使用React.createElement来创建,因为使用了Babel来转换。
1 2 3 4 5 6 7 8 9
| return ( <div className="shopping-list"> <h1>Shopping List for {this.props.name}</h1> </div> ) 编译后 return React.createElement('div', {className: 'shopping-list'}, React.createElement('h1', ) );
|
组件通信
官网的例子中有三个组件:Square、Board、Game,其中Square 组件渲染了一个单独的 <button>
。Board 组件渲染了 9 个方块。Game 组件渲染了含有默认值的一个棋盘。
通过props实现传值(vue中也可以通过props实现父子组件的传值,子组件通过props接收父组件的值)
1 2 3 4 5 6 7 8 9 10 11 12
| class Board extends React.Component { renderSquare(i) { return <Square value={i} />; } } class Square extends React.Component { render() { return ( <button className="square"> {this.props.value} </button> ); } }
|
父组件Board通过value这个prop将i传递给Square,Square通过this.props.value获取i的值
组件交互
实现点击棋格落下’X’棋子,在Square中利用state保存点击过的地方,button绑定onclick点击事件,每次点击修改state中的值。与vue不同的是,react中直接包含了state属性,而vue中的state是存在与vuex中的。发现react中方法之间不需要写逗号👍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Square extends React.Component { constructor(props) { super(props); this.state = { value: null, }; }
render() { return ( <button className="square" onClick={() => this.setState({value: 'X'})} > {this.state.value} </button> ); } }
|
需要注意的是,在JS中定义子类(Square)的构造函数时,需要调用super,在含有构造函数的react组件中,构造函数要以super(props)开头。
onclick事件中注意添加() =>,实现触发事件时才执行里面的操作,不添加的话将每次渲染都触发操作。
状态提升
读了官网的描述后,
1
| 当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的 state 数据提升至其共同的父组件当中保存。之后父组件可以通过 props 将状态数据传递到子组件当中。这样应用当中所有组件的状态数据就能够更方便地同步共享了。
|
感觉状态提升应该是指提升子组件的状态,然后将子组件的状态state直接保存在父组件中实现状态同步。
具体就是让子组件Square更新父组件Board的state,由于每个子组件中的state都是私有的,不能直接通过 Square 来更新 Board 的 state,需要从父组件Board向子组件传递一个函数,当子组件被点击时,触发handleClick函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Board: constructor(props) { super(props); this.state = { squares: Array(9).fill(null), }; } handleClick(i) { const squares = this.state.squares.slice(); squares[i] = 'X'; this.setState({squares: squares}); }
renderSquare(i) { return ( <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} /> ); } Square: return ( <button className="square" onClick={() => this.props.onClick()} > {this.props.value} </button>
|
可以看到函数和值的传递都是类似的,通过子组件通过this.props调用值或者是函数。
此时,Board 组件完全控制了 Square 组件,将 Square 组件称做“受控组件”
不可变性
注意,此时handleClick使用.slice()返回一个新的数据副本,而不是直接修改数据,这样可以跟踪数据变化,也便于撤销回复操作的开发。
1
| 不可变性最主要的优势在于它可以帮助我们在 React 中创建 pure components。我们可以很轻松的确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染。
|
官网这段话,后半段很好理解,前半段不懂,先占坑。
函数组件
将Square 类替换成函数,函数直接接收参数,组件直接调用函数
1 2 3 4 5 6 7
| function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); }
|
函数中直接写props而不是this.props,因为已经不是一个对象了,同时onclick去掉了()=>,因为触发函数就直接执行onclick。
保存历史
使用history数组保存历史,将其放在顶层的Game组件中使用,也是一种状态提升,所以去掉子组件 Board的state,通过props来传递值。官网还用了一个map方法来映射代表按钮的 React 元素,然后可以展示出一个按钮的列表,点击这些按钮,可以“跳转”到对应的历史步骤。
1
| <li key={user.id}>{user.name}: {user.taskCount} tasks left</li>
|
使用key来标识列表项,实现可以分步展示history。