前端技术栈更新飞快,日新月异,各种层出不穷的框架也是一个接一个,就目前来看是一个React、Vue、AngulaJs 三足鼎立的大局面。 AngulaJs了解不多暂且不说,但是像React、Vue这种我们经常使用的开发框架,我们要能理解其精髓,才能用的好。
拿React举例来说,最为核心的思想就是 数据驱动视图、组件化 ,而组件化解决的最大问题就是 代码复用。
而用好组件化的前提就是能够做好 模块拆分,模块拆分说白了就是一个化繁为简,提取公共部分和类似部分的过程。
举例而言:
这是PMP系统潜客详情的一个页面,可以很清晰得到看到,凡是用红色框框住的部分都是可以组件化的部分,
有些甚至都不需要维护自身的state,更无须和状态管理器(Redux)相关联。
这样一来又引出了React世界里的组件的分类:
组件分类
按有无状态分类可分
stateful component 状态组件
stateless component 无状态组件
所谓的状态组件就是在组件的内部维护自身数据的组件,通过setState 修改自身的state,进而更新视图,但是不会影响到其他的组件和页面,
这种组件的使用场景一般是:
不需要与其他组件通信
不依赖其他组件的数据
不与其他组件的变化相关联
所谓的无状态组件就是连自身的数据都不需要维护的组件,也可以称之为展示型组件,比如说一个Label,
单纯的只是做文本的展示,没有任何的变化和交互,就算有一些样式的变化,也完全可以通过暴露属性来定制。
比如:
| ```javascript import React from ‘react’;
const Label = ({ text=’label component’, style={} }) => { return (); }
export default Label;
|
| --- |
**社区和广大开发者推荐,组件最好使用单文件夹的方式来书写,这样可以更好的划分代码,也符合复用的思想,出了问题也更好定位问题位置。**
**而Vue得益于一个组件可以将html代码、js代码以及样式代码书写在一个文件内,所以一个文件就可以定义一个完整的组件。**
又上面代码可以看出,这就是一个简单的函数而已,接受一个option参数,通过ES6的对象延展语法取出两个属性:text,style并且都有默认值。
在React的世界里所有的组件都可以用函数来实现,但是这种方式实现的组件仅仅满足于展示性的需求,
想要更多的交互和数据的更改、提交之类的复杂操作则需要**类组件(状态组件)**。
比如:
| ```javascript
import React from 'react';
import Label from '../Label/Label.react';
class StatefulComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'default text'
}
}
render() {
const { text } = this.state;
return (
<div >
<Label text={text} />
<input value={text} onChange={e => {
let text = e.currentTarget.value;
this.setState({ text });
}} />
</div>
);
}
}
export default StatefulComponent;
| | —- |
不难看出,状态组件的编写方式就是通过ES6语法Class 的方式编写的,这种组件有着完备的生命周期过程(继承自React.Component),
可以在这些方法内做很多的文章,这里不多讲,后续会单独的做一些总结。
这个组件实现的效果很简单,自身state上的text会展示在Label组件上,输入框的value也和state.text绑定,输入的过程会调用onChange方法(一个匿名函数),
进而通过获取value值,setState,更新state.text值。
这只是一个简单的示例,其实上面的代码有一个缺点,那就是input标签的onChange方法使用了箭头匿名函数的方式书写,
这样的确很快捷和方便,但是对于组件的性能而言,并没有好处。看下面的代码:
| ```javascript import React from ‘react’; import Label from ‘../Label/Label.react’;
class StatefulComponent extends React.Component { constructor(props) { super(props); this.state = { text: ‘’ } }
handleChange = e => { let text = e.currentTarget.value; this.setState({ text }); }
render() { const { text } = this.state; return (
export default StatefulComponent;
|
| --- |
能看到,我们把onChange方法提取出来,写到了组件的内部作为一个私有方法的方式存在,
而在标签的onChange那里,只传递一个handleChange函数的引用就行了。
这样的好处就是,当我们的组件有接收外界传入的props作为数据的时候,当props里的数据发生变化,
进而引起该组件的更新渲染,如果数据并没有与这个input有任何关系,那么这个input标签就不会进行渲染。
原因是这样的,每次当挂载一个组件的时候,都会重新在虚拟DOM层做一层对比(**Diff算法**),进而判断这个标签(组件)有没有必要重新渲染。
当我们通过匿名函数的方式实现onChange方法的时候,每一次挂载都会重新挂载一个崭新的input标签出来,虽然并没有打的影响,但是出于性能的考虑,我们就应该做的更好。
那就是通过传递引用的方式实现onChange方法,这样当Diff算法对这个input便签做对比的时候,会因为这个input标签的onChange方法的引用并没有发生变化,而不会重新挂载一个崭新的input标签上来。
这也正胡思我们需要达到的目的,因为完全没必要换一个新的input标签。这样一来我们就从最小的细节处优化了组件的性能。
说道性能优化不得不提一下**React.PureComponent**,有兴趣的自行Google。
<a name="yuargt"></a>
### 按书写方式分类
- 函数组件
- 类组件
<a name="3zlqtn"></a>
### 按功能分类
- 展示型组件
- 容器组件
- 复杂交互组件
关于展示型组件,Label组件的代码足以说明,复杂的交互组件,stateful组件的存在即使为了解决这个问题,而容器组件也很纯粹,只是一个Box(盒子)而已。
比如:
| ```javascript
//容器组件Box代码
import styles from './Box.css';
import React from 'react';
const Box = ({ props, children }) => <div className='box' {...props}>{children}</div>;
export default Box;
//在类组件内部使用Box
import React from 'react';
import Label from '../Label/Label.react';
import Box from '../Box/Box';
class StatefulComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: 'default text'
}
}
handleChange = e => {
let text = e.currentTarget.value;
this.setState({ text });
}
render() {
const { text } = this.state;
return (
<Box props={{ style: { padding: 10, border: '1px solid red' } }}>
<Label text={text} />
<br />
<input value={text} onChange={this.handleChange} />
</Box>
);
}
}
export default StatefulComponent;
| | —- |
生命周期
React 16.3 version
如上图所示,React stateful 组件的生命周期分为三个阶段:
Mounting (挂载阶段)
Updating(更新阶段)
Unmounting (卸载阶段)
其实严格意义上还有一个阶段:存在阶段,也就是没有发生任何变化,存在在DOM树上的阶段。
下面看一段代码:
| ```javascript import styles from ‘./LifeCircle.css’
import React from ‘react’ import reactDom from ‘react-dom’
class LifeCircle extends React.Component { constructor(props) { //构造方法,初始化阶段 super(props) console.log(‘constructor’) this.state = { text: ‘Button’ } }
componentWillMount() { //即将挂载,挂载前的准备阶段 console.log(‘componentWillMount’) }
handleClick = () => { //触发点击事件,通过setState方法修改状态,引发生命周期对的再次执行,进而一起视图的变化 console.log(‘click’) this.setState({ text: ‘anything’ }) }
componentWillReceiveProps(nextProps) { console.log(‘componentWillReceiveProps’) //这是当父组件传递进来的属性值发生变化的时候执行的钩子函数 // 可以在这里做一些操作数据的行为 }
shouldComponentUpdate(nextProps, nextState) { console.log(‘shouldComponentUpdate’) //该生命周期方法有两个参数,即nextProps, nextState,可以在这个方法内做一些判断 //进行组件的性能优化,该方法必须有返回值, //类型为Boolean(true 代表可以发生更新过程,false代表即使state或者props发生了变化也不会引发视图的更新) return true }
componentWillUpdate() { console.log(‘componentWillUpdate’) //即将更新视图前的阶段 }
render() { console.log(‘render’) //render方法贯穿于挂载和更新阶段,是视图发生变化的核心方法 return (
componentDidUpdate(preProps, preState) { console.log(‘componentDidUpdate’) //这两个参数分别是上一刻的属性和状态值 //视图更新完毕后的钩子函数 }
componentDidMount() { console.log(‘componentDidMount’) //挂载完毕的方法,在这里虚拟DOM对应的节点均已挂载到真实的DOM树上,可以访问到任何一个节点。比如: const btnDom = document.getElementById(‘button’) btnDom ? console.log(‘button is in the dom tree for real!’) : ‘’ }
componentWillUnmount() { console.log(‘componentWillUnmount’) //组件卸载前的钩子函数 }
} export default LifeCircle ``` | | —- |
效果图结合代码食用更佳
首次挂载:
点击更新:
卸载挂载: