写了这么久React,该来一些总结了

前言

学习React也有将近一个月的时间,坎坎坷坷,对于熟悉一个框架来说,基础知识还是很重要的,这里的基础只是并不是React的基础只是,而是以往的一些开发思想和经验,以前写过一些html链接一个JavaScript代码,遇到类似的组件和功能,又要copy一份再修改,导致代码臃肿,用原生的方法来实现组件化又显得很繁琐,如果单单只是个展示页面还没有什么,当遇到一些交互复杂的需求,这个时候密集性操作DOM会让代码可读性一落千丈,基本上就是一次性代码,下次再维护更新就好比重构一样复杂。因此这也是为什么会出现React这样以“数据驱动”为原则的前端框架,这里我主要总结了自己学习的一些心得和自己想法。

组件和容器

这一点我觉得如果将组件称为木偶组件,将容器称为智能组件会更加贴切一点,木偶是什么呢,就是它是死板的不会动的,只有你驱动它,他才会动,也就是我们平常说的样式组件,只关心样式不关心逻辑。智能组件就是一个“又自己逻辑想法的”组件,它负责页面的逻辑交互、数据获取等较为复杂的事情。我们用一段代码来看:

  • 组件(木偶组件)

特点: 接收父级传来的props改变样式,没有自己的状态和逻辑。

1
2
3
4
const Button = ({children}, context) =>
<button style={{background: context.color}}>
{children}
</button>;

  • 容器(智能组件)

特点: 继承了React的Component,有自己的状态(state),有自己的声明周期,向子组件(木偶组件)传递数据

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
class SidebarContainer extends React.Component {
constructor(props){
super(props)
this.setState({
items:[]
})
}
componentDidMount() {
fetch('/url')
.then(res => {
this.setState({
items: res.items,
})
})
}
render() {
return (
<Sidebar>
{this.state.items.map(item => (
<SidebarItem data={this.state.items}>{item}</SidebarItem>
))}
</Sidebar>
);
}
}

在实际项目开发中,通常会在src目录中创建一个container文件夹和components文件夹,用来区分木偶智能组件,明确划分了他们的界限,也方便以后的维护。

列表的唯一id:key

刚刚接触React的时候,写循环列表总是会忘记写上key,每次看到控制台报错才意识到,但是那时候压根不知道为啥要带上这么一个key,感觉也没啥用呀,后来谷歌后才发现,React是个非常注重效率的框架,Keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化,进一步提升虚拟DOM的效率,因此应当给数组中的每一个元素赋予一个确定的标识。

一个元素的key最好就是在列表中拥有唯一的字符串,通常我使用mongo数据库传来的id做为key,这样就能确保每个都不一样,如果是在没办法,也能用序列号索引index作为key。

1
2
3
4
5
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);

或者:

1
2
3
4
5
const todoItems = todos.map((todo, index) =>
<li key={index}>
{todo.text}
</li>
);

在HTML中写JavaScript

如果用传统是想来看这一点,我我敢保证你分分钟会被谴责,因为传统的文波开发理念是逻辑与html完全分离,但是React打破了这一常规,开始阶段也被骂的很惨,但是慢慢的程序员发现这是一件非常便利的事情,比如说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const arr = [
{
name: "huajinbo",
sex: 0
},
{
name: "zhuzhu",
sex: 1
}
];
<div>
{arr.map(v => {
return v.sex === 0 ? <p>{v.name}</p> : <h1>{v.name}</h1>;
})}
</div>

如果说让我选择,我当然会使用JavaScript in HTML这一方式,真的很大程度上提高了开发效率。

表单的最佳事件

在React中处理表单是件非常优雅的事情,我们可以根据不同的需求,选择不同的方式,以下分别介绍这两种方式:

受控表单

受控是指这个表单的一切,都受到react的监控与驱动,我们看下面:

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

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
errMsg: ""
};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
var emailValue = event.target.value
this.setState({ email: emailValue });
var reg=/^([a-zA-Z0-9]+[-_.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[-_.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,6}$/
if (emailValue === "") {
this.setState({
errMsg: '邮箱不为空'
})
} else if (!reg.test(emailValue)) {
this.setState({
errMsg: '邮箱格式不对'
})
} else {
this.setState({
errMsg: ''
})
}
}

handleSubmit(event) {
alert("Your email is: " + this.state.email);
event.preventDefault();
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Email:
<input
type="text"
value={this.state.email}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
<p style={{color:'red'}}>{this.state.errMsg}</p>
</form>
</div>
);
}
}
export default App;

演示:

image

demo略长,我们可以看到在input中监控了这个输入框的的输入变化,实时获得input输入的数据,根据这个数据,我们能够实时地判断输入是否符合规范,做到实时给出错误提醒,当然这个例子并不是完美,和真正表单还差一点,我理解的完美表单是:般输入框在第一次输入时候并不会验证,输入完成后,光标移出输入框,判断是否符合要求,若错误,用户再次输入,这个时候就采用了实时验证提醒错误的标识。不知道是否正确,因为没有机会接触大项目,所以有错误还希望小伙伴们提出。

非受控表单

非受控是值,输入框并不会受React对其的控制,这是第二种选择,我们来看看代码:

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

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
errMsg:""
};
this.handleSubmit = this.handleSubmit.bind(this);
}

handleSubmit(event) {
event.preventDefault();
console.log(this.refs.emailInput.value)
let emailValue = this.refs.emailInput.value
var reg=/^([a-zA-Z0-9]+[-_.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[-_.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,6}$/
if (emailValue === "") {
this.setState({
errMsg: '邮箱不为空'
})
} else if (!reg.test(emailValue)) {
this.setState({
errMsg: '邮箱格式不对'
})
} else {
this.setState({
errMsg: ''
})
}
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Email:
<input
type="text"
ref="emailInput"
/>
</label>
<input type="submit" value="Submit" />
<p style={{color:'red'}}>{this.state.errMsg}</p>
</form>
</div>
);
}
}
export default App;

演示:

image

这种方法无法实时获取输入数据,所以并不能向上面那样实时提供错误提醒,并且还直接用到了ref,这是React并不推荐的,即不推荐直接操作真实DOM,但是经过我几个小项目的尝试,有时候受控组件因为state渲染组件的问题会导致input失去焦点或者页面卡顿,这个时候如果你或许能试试这种“备胎”方式。

多个受控组件

如果你有多个受控的input组件,那么我建议你这么写,即美观又符合不写重复代码的原则:

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
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}


<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>

让协作更规范的propTypes

之前和小伙伴协作开发一个小项目,他写后端,我写前端,开发前就约定好传来前端的数据,这么做极大地降低了我和小伙伴友情破灭的概率,但是,如果是和小伙伴一起开发前端项目呢,比如你写的一个木偶组件需要给你的小伙伴倒入到容器组件中,这个时候,同样需要这么一套规范,它就是让协作更规范的propTypes,一来降低了代码出错的概率,而来也与小伙伴形成一个约定,何乐而不为呢?

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import PropTypes from 'prop-types';

class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}

Greeting.propTypes = {
name: PropTypes.string
};

常用的几种验证:

1
2
3
4
5
6
7
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,

设置props默认值

一般来说props是要设置默认值的,特别是像一些需要遍历渲染出列表的组件,设置props值是非常重要的,因此如果传入了非数组(网络请求如还未获取到的数据),map遍历是会出错的,在es5是这样设置的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Greeting extends React.Component {
render() {
return (
<div>
{arr.map(v=>(
//somethis
))
</div>
);
}
}
Greeting.defaultProps = {
arr: []
};

ES6后,我们可以在赋值的时候直接设置默认值如

1
2
3
4
5
6
7
8
9
10
11
12
class Greeting extends React.Component {
const { name=[] } = this.props
render() {
return (
<div>
{arr.map(v=>(
//somethis
))
</div>
);
}
}

减少不必要的节点:Fragment

在HTML中嵌套过多层的组件事件非常不好的事情,降低了可读性不说,而且性能也不好,所以在React16.2中推出了Fragment,它就是一个“空标签”包装,在DOM树中不会渲染出任何东西,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react'

class TestCom extends React.Component{
render(){
return(
<React.Fragment>
<h1>111</h1>
<h1>222</h1>
</React.Fragment>
)
}
}

export default TestCom

其实也可以这么写:

1
2
3
4
5
6
7
8
render(){
return(
<>
<h1>111</h1>
<h1>222</h1>
</>
)
}

只不过现在版本的webpack还不支持而已,会直接报错。

总结

基本上以上内容在文档上都有写,但是有些细节上的问题,我这里还是提出来了,一来巩固自己的知识,而来分享给大家,或许能帮助你少踩几个坑呢~

one today is worth two tomorrows. :)