橙子

react 之 TodoList

这次是用TodoList来总结一些react中的点,如vue中的v-model在react中要怎么做,组件之间怎么传值,属性校验等

TodoList小练习的页面如下:

state 、setState

首先,react中没有v-model,所以input中的数据双向绑定需要 state,onChange,setState来配合

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
import React from 'react';

class TodoList extends React.Component {

// 状态
state = {
inpVal: '',
list: ['蓝天', '白云', '青山'],
count: 0
}
handleChange = e => {
this.setState({
inpVal: e.target.value
})
}

handleClick = () => {
this.setState({
list: [...this.state.list, this.state.inpVal],
inpVal: ''
})
}

handleDelete = index => {
const list = this.state.list;
list.splice(index, 1);

this.setState({
list
})
}
render() {
return (
<>
<div>
<input type="text" value={ this.state.inpVal } onChange={ this.handleChange } />
<button onClick={ this.handleClick }>添加</button>
</div>
<ul>
{
this.state.list.map( (item, index) => (
<li key={ item }>
{ item }
<button onClick={ () => { this.handleDelete(index) } }>X</button>
</li>
))
}
</ul>
</>
)
}
}

export default TodoList;

注意:

​ 1 在绑定事件的时候,要注意this的情况,可以写onChange={ this.handleChange } 成 或者 用文中的这种箭头函数的方式

​ 2 input绑定的是state中的值,所以刚开始没绑定onChange事件时会发现无法输入,因为state不能直接改变,需要通过setState这个方法改变state中的值,添加按钮也是同样的道理,list需要通过setState来改变

​ 3 当需要传参时,使用 onClick={ () => { this.handleDelete(index) } }, 而不是 onClick={ this.handleDelete(index) }, 后者在绑定的时候就会执行,所以需要使用前者,当然也可以写成 bind(this)

​ 4 当使用map的方法时,需要加个key值来表明唯一性,尽量不要用index(因为当list的值发生变化,页面需要重新渲染的时候,react是根据key来和上一次的进行对比的,若key的这个元素依然)

现在有个问题,如下

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
state = {
count: 0
}
<div>
<span>{ this.state.count }</span>
<button onClick={ this.handleAdd }>添加</button>
</div>
handleAdd = () =>{

//this.setState({
// count: 2
//})
this.setState((prevState) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState) => {
return {
count: prevState.count + 2
}
})
this.setState((prevState) => {
return {
count: prevState.count + 3
}
})
}

当点击添加按钮时,count 会加 6 吗,并没有,这里只加了 3 ,而这几句换个位置会发现,加的值变了,但始终不是 加 6 ,这是因为setState会有个事件队列,当对比发现改变的是同一个state属性时,会进行后面的覆盖前面的

受控组件、非受控组件

​ 受控组件和非受控组件的区别就是:是否受状态影响

​ 受控组件就是上面写的那种, input框的值受state中的影响的,所以是受控组件,如下:

1
<input type="text" value={ this.state.inpVal } onChange={ this.handleChange } />

​ 非受控组件就是给一个组件起了个名字,我们通过这个名字找到这个组件,然后去获取一些信息,如下:

1
2
3
4
5
6
7
8
9
10
// 方式一:
// 这样就相当于 this.inp = <input type="text" />,this.inp.value 就可以获取到这个input的值了
<input type="text" ref={ (dom) => {this.inp = dom} } />

// 方式二:
// this.inp.current.value 就可以获得这个input的值了
inp = React.createRef() // 16.3以后
<input type="text" ref={ this.inp } } />

// 这样的情况下,受控组件这种方式在input有所改变的时候就会触发setState,而非受控组件并不会

属性校验

当一个组件传值给另一个组件时,需要以下几步:

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const person = {
name: '小安',
age: 18,
sex: '女',
figure: {
weight: 95,
height: 168
},
hobby: ['看电影', '看书'],
salary: 100
}

render(<Person {...person}></Person>, document.getElementById('root'))
// 这里也可以把 {...person} 写成 person,不过后者试套了一层

子组件:

1
2
3
4
5
6
const { name, age, sex, figure, hobby, salary } = this.props

<div>{name} 个人资料</div>
<div>年龄:{ age }</div>
<div>性别:{ sex }</div>
...

当然也可以设置默认属性,比如name这个属性,父组件有传 name,那就用父组件传过来的,没有那就用自己的,如下(子组件中):

1
2
3
static defaultProps = {
name: 'duyi'
}

子组件通过 this.props 获取父组件传过来的值(props中的值是只读的),但当组件需要number而父组件传的是string 时,就需要属性校验了,属性校验需要以下几步:

1 安装 prop-typesnpm install prop-type --save

2 在子组件引入它:

1
import PropTypes from 'prop-types';

3 定义所需要的属性各是什么类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 这些在github官网上都有
static propTypes = {
name: PropTypes.string,
age: PropTypes.number,
sex: PropTypes.oneOf(['男', '女']),
figure: PropTypes.objectOf(PropTypes.number),
hobby: PropTypes.arrayOf(PropTypes.string),
salary (props, propsName, componentName) {
if(props[propsName] < 10000) {
return new Error(
`${componentName}组件传递过来的${propsName}属性的值太小啦,应该大于1万`
);
}
}
}

这样属性校验就好了

组件交互

还是以 这个TodoList为例,把这个功能分成了下面这样的几个组件:

图一

父子组件之间的传值如前面那样,但如果跳级传值的话,一层一层传下去比较麻烦,所以react中可以通过content这种方式来传(content 是 16版本之后出现的 ),流程是下面这样:

而使用步骤是,把祖父组件用Provider 包裹起来,把孙子组件Consumer 包裹起来,具体步骤如下:

1 content 是react的,所以需要先引入, content.js:

1
2
3
4
5
6
7
8
import React from 'react'

let {Provider, Consumer} = React.createContext();

export {
Provider,
Consumer
}

2 TodoWrap.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_handledelete = (index) => {
const list = [...this.state.list];
list.splice(index, 1);
this.setState({
list
});
};

render() {
return (
<Provider value={{ _handledelete: this._handledelete }} > //value名字是固定的
<div>
<input type="text" ref={this.task} />
<button onClick={this._handleClick}>添加</button>
</div>
<Todolistcomp list={this.state.list} fn={this._handledelete}/>
</Provider>
);
}

3 TodoItem.js, consumer中需要写成这种箭头函数的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from "react";
import { Consumer } from "./content";

class Todoitemcomp extends Component {
_handledelete = index => {
this.props._handledelete(index);
};
render() {
const { task, index } = this.props;
return (
<Consumer>
{({ _handledelete}) => (
<li>
{task}
<button onClick={() => _handledelete(index)}>X</button>
</li>
)}
</Consumer>
);
}
}

export default Todoitemcomp;