React基础
- 所谓组件,就是能完成某个特定功能的、独立的、可重用的代码
- 组件的划分要满足高内聚(逻辑紧密相关的内容放在同一组件中)和低耦合(不同组建之间依赖关系要弱化)
- Component是所有组件的基类
- React判断一个元素是HTML元素还是React组件的原则,就是看第一个字母是否大写
- React的理念:UI = render(data),React应用通过重复渲染来实现用户交互
- Virtual DOM
I. 利用Virtual DOM,让每次渲染都只重新渲染最少的DOM元素
II. 前端性能优化的其中一个原则就是尽量减少DOM操作,因为它会引起浏览器对网页的重新布局
- 在React中,render执行的结果得到的并不是真正的DOM节点,而仅仅是JS对象,称之为虚拟DOM
- 虚拟DOM具有高效的diff算法,随时刷新整个页面,从而确保只对界面上真正变化的部分进行实际的DOM操作
- JSX与HTML区别
import React, { Component } from 'react';
class ClickCounter extends Component {
constructor(props) {
super(props);
this.onClickButton = this.onClickButton.bind(this);
this.state = {count: 0};
}
onClickButton() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div>
<button onClick={this.onClickButton}>Click Me</button>
<div>
Click Count: {this.state.count}
</div>
</div>
);
}
}
export default ClickCounter;
复制代码
HTML:
- onclick添加的事件处理函数实在全局环境下执行,污染全局环境
- 给很多DOM元素添加onclick事件会影响网页性能
- 当使用onclick的DOM元素从DOM树中动态删除的话,需要把对应的事件处理器注销,否则会造成内存泄露
JSX:
- onClick挂在的每个函数都可以控制在组件范围内,不会污染全局环境
- 使用了事件委托的方式处理点击事件,无论多少onClick出现,最后都只在DOM树上添加一个事件处理函数,挂在最顶层DOM节点上
- React在组件生命周期的卸载过程中,会清楚相关的所有事件处理函数,使得内存泄漏不再是问题
什么是事件委托?
- 在父元素上绑定事件,监听子元素的冒泡事件,并获取到事件发生的子元素
- 事件委托可以避免对每个节点添加事件监听器,减少DOM结点的操作次数,提高性能
Diff算法:
- 传统的diff算法通过循环递归对节点进行依次比较,算法复杂度为O(n^3),其中n是树中节点的个数
- diff算法的三个策略
- DOM节点跨层级的移动操作发生频率很低,是次要矛盾;
// Tree Diff
React对Virtual DOM树进行层级控制,只会对相同层级的DOM节点进行比较。
即:同一个父元素下的所有子节点,当发现节点已经不存在了,则会删除掉该节点下所有的子节点,不会再进行比较。
复制代码
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构,这里也是抓前者放后者的思想;
// Component Diff
如果是同一个类型的组件,则按照原策略进行Virtual DOM比较。
如果不是同一类型的组件,则将其判断为dirty component,从而替换整个组价下的所有子节点。
如果是同一个类型的组件,有可能经过一轮Virtual DOM比较下来,并没有发生变化。
如果我们能够提前确切知道这一点,那么就可以省下大量的diff运算时间。因此,React允许用户通过shouldComponentUpdate()来判断该组件是否需要进行diff算法分析。
复制代码
例如:如果我们把<Header>替换为<ExampleBlock>,React将会移除header接着创建example block。
复制代码
- 对于同一层级的一组子节点,通过唯一id进行区分,即没事就warn的key。
React组件的数据
1. prop
a) prop是组件的对外接口,是从外部传递给组件的数据
b) React组件通过定义自己能接受的prop,就定义了自己的对外公共接口
<SampleButton
id="sample" borderWidth={2} onClick={onButtonClick} style={{color:"red"}}
/>
复制代码
其中,
i. id、borderWidth、onClick和style都是prop
ii. 当prop的类型不是字符串类型时,在JSX中必须用花括号{}把prop值包住
iii. 函数类型的prop相当于让父组件交给子组件一个回调函数,子组件调用该函数类型的prop时,可以带上必要的参数,这样就可以反过来把信息传递给父组件
class Counter extends Component {
constructor(props) {
super(props);
this.onClickIncrementBtn = this.onClickIncrementBtn.bind(this);
this.onClickDecrementBtn = this.onClickDecrementBtn.bind(this);
this.state = {
count: props.initValue || 0;
}
}
render(){
const { caption } = this.props;
return (
<div>
<button onClick={this.onClickIncrementBtn}>+</button>
<button onClick={this.onClickDecrementBtn}>-</button>
<span>{caption} count: {this.state.count}</span>
</div>
);
}
}
复制代码
React的propTypes可以用来设定该组件支持哪些prop,以及每个prop应该是什么格式
Counter.propTypes = {
caption: PropTypes.string.isRequired,
initValue: PropTypes.number
}
复制代码
propTypes检查只是一个辅助开发的功能,所以在生产代码中应该将其去掉,可以考虑使用babel-react-optimize
2. state
- state是一个JS对象,通过this.state读取组件的当前state,通过this.setState修改组件state
- this.setState()首先是改变lthis.state的值,然后还驱动组件经历更新过程,从而让this.state里新的值出现在界面上
prop和state的对比:
- prop用定义外部接口,state用于记录内部状态
- prop的赋值在外部世界使用组件时,state的赋值在组件内部
- 组件不应该改变prop值,而state存在的目的就是让组件来修改
复制代码
3. refs
- React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。
- 这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例。
class MyComponent extends React.Component {
handleClick() {
// 使用原生的 DOM API 获取焦点
this.refs.myInput.focus();
}
render() {
// 当组件插入到 DOM 后,ref 属性添加一个组件的引用于到 this.refs
return (
<div>
<input type="text" ref="myInput" />
<input
type="button"
value="点我输入框获取焦点"
onClick={this.handleClick.bind(this)}
/>
</div>
);
}
}
// 也可以使用 getDOMNode()方法或React.findDOMNode()方法获取DOM元素
var inputDOM = this.refs.myInput.getDOMNode();
var inputDOM = React.findDOMNode(this.refs.myInput);
复制代码
4. 组件的生命周期(重点)
装载过程:组件第一次在DOM树中渲染的过程;
更新过程:组件被重新渲染的过程;
卸载过程:组件从DOM中删除的过程;
装载过程(Mount)
- constructor
- 初始化state
- 绑定成员函数的this环境
- ES6中,类的每个成员函数在之行时dethis并不是和类实例自动绑定的,而在构造函数中this就是当前组件实例
- getInitialState和getDefaultProps
- 只有用React.createClass方法来创造的组件类才会发生作用,已经被废弃
- render
- 返回一个JSX描述的结构,并不往DOM树上渲染或装载内容,最终由React来操作渲染过程
- 若返回一个null或false,相当于告诉React,这个组件这次不需要渲染任何DOM元素
- 它是一个纯函数,完全根据this.state和this.props来决定返回的结果,不应该引起任何状态的改变
- componentWillMount
- 在render函数之前调用(紧贴render),在其中所做的事情均可放在constructor中
- componentDidMount
- 在render函数之后调用(非紧贴render),由于render函数仅仅返回一个JSX描述的结构,并不往DOM树上渲染或装载内容,因此只有React调用各个组件的render函数之后,才会完成装载
- 在该函数调用的时候,组件已经被装载到DOM树上,因此可以放心使用任何DOM
- AJAX请求应该在此阶段调用
更新过程(Update)
- componentWillReceiveProps(nextProps)
- 只要父组件的render函数被调用,子组件就会经历更新过程,无论父组件传给子组件的props是否发生改变,都会触发子组件的componentWillReceiveProps函数
- this.setState方法触发的更新过程不会调用这个函数
- 由于componentWillReceiveProps并不是当props值发生变化时才被调用,所以有必要将传入参数nextProps和this.props(上次渲染时的props值)做对比,若不同则需要调用this.setState更新内部状态
- shouldComponentUpdate(nextProps, nextState)
- 它决定了一个组件什么时候不需要渲染
- 它和render是React生命周期中仅有的两个需要有return的函数
- 只有当传入的props值或state值相比于this.props或this.state发生改变时,才返回true,从而调用render函数
- 通过this.setState引发的更新过程并不是立即更新组件的state值,在执行到函数shouldComponentUpdate的时候,this.state值依然是执行setState函数之前的值
shouldComponentUpdate(nextProps, nextState) {
return (nextProps.caption !== this.props.caption)
||
(nextState.count !== this.state.count);
}
复制代码
- componentWillUpdate和componentDidUpdate
- 当shouldComponentUpdate函数返回true时,React会依次调用componentWillUpdate、render和componentDidUpdate
补充: componentWillUpdate和componentShouldUpdate均不可使用setState,该操作会触发componentUpdate,会造成循环调用
卸载过程
- componentWillUnmount
- 当React组件要从DOM树上删除掉之前,该函数会被调用,去执行一些请理性的工作
- 例如:在componentDidMount中用非React方法创造了一些DOM元素,有可能会造成内存泄漏
React单向数据流与组件通信
单向数据流
React是单向数据流,从父节点传递到子节点(通过props)。如果顶层的某个props改变了,React会重渲染所有的子节点(未做性能优化)。严格意义上React只提供,也强烈建议使用这种数据交流方式。
父子通信
- 父组件更新组件状态 -----props-----> 子组件更新
- 子组件更新父组件状态 -----需要父组件传递回调函数-----> 子组件调用触发
- 父组件通过props传递一个回调函数到子组件中,这个回调函数可以更新父组件,子组件就是通过触发这个回调函数,从而使父组件得到更新。
// 父组件
class Box extends React.Component {
constructor(props) {
super(props);
this.state = {
text: ''
}
}
refreshBox() {
this.setState({
text: '子组件更新父组件成功'
});
}
render() {
var data = this.props.data;
var text = this.state.text;
return (
<div>
<div data={data}></div>
<Son text={this.state.text}
refreshBox={this.refreshBox.bind(this)}
/>
</div>
);
}
}
// 子组件
class Son extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
{this.props.text}
<button onClick={this.props.refreshBox}>更新父组件</button>
</div>
)
}
}
复制代码
兄弟组件
- 方式一:借助父组件进行传递,通过父组件回调函数改变兄弟组件的props(只适用于组件层次少的)
- 方式二:React官方给我们提供了一种上下文方式,可以让子组件直接访问祖先的数据或函数,无需从祖先组件一层层地传递数据到子组件中
- Parent.childContextType就是这样一个上下文声明,子组件调用祖先组件的方法时,通过 this.context.[callback] 这样就可以进行祖先与子组件间的沟通了。
class Brother1 extends React.Component{
constructor(props){
super(props);
this.state = {}
}
render(){
return (
<div>
<button onClick={this.context.refresh}>
更新兄弟组件
</button>
</div>
)
}
}
Brother1.contextTypes = {
refresh: React.PropTypes.any
}
class Brother2 extends React.Component{
constructor(props){
super(props);
this.state = {}
}
render(){
return (
<div>
{this.context.text || "兄弟组件未更新"}
</div>
)
}
}
Brother2.contextTypes = {
text: React.PropTypes.any
}
class Parent extends React.Component{
constructor(props){
super(props);
this.state = {}
}
getChildContext(){
return {
refresh: this.refresh(),
text: this.state.text,
}
}
refresh(){
return (e)=>{
this.setState({
text: "兄弟组件沟通成功",
})
}
}
render(){
return (
<div>
<h2>兄弟组件沟通</h2>
<Brother1 />
<Brother2 text={this.state.text}/>
</div>
)
}
}
Parent.childContextTypes = {
refresh: React.PropTypes.any,
text: React.PropTypes.any,
}
复制代码
MVVM (Model - ViewModel - View,数据驱动视图)
双向绑定
- 双向
正向:Data ---> View
反向:View ---> Data
- 通过模板将数据渲染到页面,模板工具(正向)
- 页面内容改变后映射到保存的数据中,绑定事件onchange(反向)
- 绑定
- 自动化处理
- 原理实现
Object.defineProperty(obj, prop, descriptor);
Descriptor:
1. configurable
2. enumerable
3. writable
4. value
5. get
6. set
- set中会在赋值时,检测两次的值是否相同
复制代码
设计模式
- 定义原生data
- Observer监听data发生变化,通知观察者列表(Dep)
- Observer的背后其实就是Object.defineProperty,数据的改变会触发set函数
- 观察者列表(Dep)中的成员要调用Watcher中的回调函数,继而更新View
- Watcher要负责添加观察者列表成员(Dep)
MVC和MVVM的区别
- MVVM,数据驱动视图,核心是VM ,常用的有 vue,react MVC的话就是传统的 Model - view - controller 三部分组成
- MVVM中的View 和 ViewModel可以互相通信。也就是可以互相调用。 MVC中的是单向通信。也就是View跟Model,必须通过Controller来承上启下