文章目录
- 1.简介
- 1.1 react与vue
- 1.1.1 相同点
- 1.1.2 不同点
- 1.1.3 函数式组件的特点(什么是函数式组件)
- a.幂等
- b.无副作用用:
- 1.1.4 虚拟dom的作用
- 1.1.5 vue当中template与render的关系:
- 1.2 MVC、MVVM、MVP模式
- 1.2.1 MVC
- 1.2.2 MVVM
- 1.2.3 MVP
- 2.JSX
- 2.1 预防XSS攻击
- 2.2 JSX表示对象
- 3.State和Props
- 3.1 Props
- 3.2 State
- 3.2.1 只能通过setState赋值
- 3.2.2 更新可能是异步
- 3.2.3 批量更新的优化
- 4. 生命周期
- 4.1 旧版生命周期(react<16.0.0)
- 4.2 新版生命周期
- 4.3.1 挂载阶段
- constructor
- getDerviedStateFromProps
- render
- componentDidMount()
- 4.3.2 组件更新阶段
- shouldComponentUpdate
- getSnapshotBeforeUpdate
- componentDidUpdate
- 4.3.3 组件卸载阶段
- componentWillUnmount
1.简介
React 是一个用于构建用户界面的 JAVASCRIPT 库,主要用于构建 UI,很多人认为 React 是 MVC 中的 V。
起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。
- 声明式设计 −React采用声明范式,可以轻松描述应用。
- 高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
- 灵活 −React可以与已知的库或框架很好地配合。
- JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
- 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
- 单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
1.1 react与vue
1.1.1 相同点
- 都是数据驱动视图。
- 采用虚拟dom+diff算法,但是react16以后推出了Fiber双缓存。
- 鼓励组件化
了解React
1.1.2 不同点
写法上: react是js的一个库,使用jsx语法,将js与html结合,更让人觉得自己在写代码;vue更像是一个前端框架,具有侵入性,有自己的语法。
核心思想上: vue为双向绑定。react推崇函数式编程,单行数据流的特性,需要双向要使用onChange和setState去实现。
diff算法: 都是按照tree这样的维度,vue从两侧像中间递归,react从左到右。
响应式原理不同:
- react主要是通过setState()方法来更新状态,状态更新之后,组件也会重新渲染。
- vue2.0:当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim(降级) 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。(对象属性拦截)
- vue3.0:
Vue3
的响应式原理是通过Proxy
来完成的。Proxy
直接监听对象,而非属性,所以将多个属性转换成getter/setter
的时候,不需要使用循环。(对象整体代理)
1.1.3 函数式组件的特点(什么是函数式组件)
a.幂等
只要输入值不变,输出值也不变。输出结果完全由输入决定的,输出的结果也不会因为多次调用而发生改变。
例:splice方法,两次传入值一样,返回值不一样,不满足幂等(不满足原因:改变原数组)
slice方法,传入值一样,返回值不变,满足幂等(没有改变原数组)
b.无副作用用:
不会对外界造成影响(如:发起ajax请求,设置localStorage等)。
1.1.4 虚拟dom的作用
保证性能下限。不需要手动做太多的优化,就能够开发出有一定性能的应用
1.1.5 vue当中template与render的关系:
vue默认是template渲染,可以没有render方法。vue在编译时,先去判断组件里有没有render方法,如果没有,就会把当前的template转换成render方法,最后产出的就是template里面的元素。以render为最高优先级,如果没有render就会把template转换为render。
1.2 MVC、MVVM、MVP模式
1.2.1 MVC
MVC是指Model、VIew和Controller:
View层: 负责显示逻辑。
Model层: 负责存储页面的业务数据,以及对应数据的操作。
Controller层: 负责应用与用户的相应操作。
当用户与页面交互, View 需要更新时,首先去找 Controller,然后 Controller 通过调用Model层,来完成对Model层的修改,然后Model层再去通知VIew层更新。
其中 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。
1.2.2 MVVM
MVVM 分为 Model、View、ViewModel:
Model: 代表数据模型,数据和业务逻辑都在Model层中定义
View: 代表UI视图,负责数据的展示
ViewModel: 负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;
Model和View并无直接联系,而是通过ViewModel来进行联系的,Model层和VIeModel层有着双向数据绑定的联系,因此Model中的数据改变时会触发View的刷新,用户在view层操作而改变的数据也会在Model层同步。
这种模式实现了Model层和View层的数据自动同步,开发者不需要自己操作DOM。
1.2.3 MVP
view需要更新数据时,首先去找Presenter,然后Presenter去找Model请求数据,Model获取到数据之后通知Presenter,Presenter再通知view更新。
View和Model不会直接交互,所有的交互都是由Presenter进行,Presenter充当了桥梁的作用。而Presenter必须同时拥有View和Model的对象的引用。
MVC中使用了观察者模式,当Model层发生改变时,View层更新。这样VIew层和Model层耦合在一起,当项目中逻辑变得负责是,可能造成代码混乱。
MVP中通过Presenter来实现对View层和Model层的解耦。MVC中的Controller只知道Model的接口,因此它没办法控制view层的更新。
MVP中,view层的接口暴露给了Presenter,因此可以再Present中将Model和View的变化绑定在一起,以此来实现VIew和Model的同步更新。这样就实现了View好Model的解耦。
2.JSX
JSX称为JS的语法拓展,在view层将内容书写进去,可以包含一些逻辑,通过逻辑与web组合实现一个基本的react描述。其实就是将js和html结合起来书写
2.1 预防XSS攻击
点此了解XSS攻击
2.2 JSX表示对象
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
Babel会把JSX转移成一个名为React.createElement()函数调用
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
通过React.render,将JSX渲染为DOM
// 使⽤ReactDOM.render
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
3.State和Props
3.1 Props
组件无论是使用函数声明还是通过class声明,都绝不能修改自身的props
props 作为组件对外通信的一个接口,为了保证组件像纯函数一样没有响应的副作用,所有的组件都必须像纯函数一样保护它们的props不被修改
3.2 State
如果视图内的数据需要修改, 并且同时页面响应变化,我们需要将数据放在state中, 使用setState来修改数据。它是组件本身的一个作用域。
3.2.1 只能通过setState赋值
不生效(视图无法更新):
// 请不要直接修改状态值
// this.state.counter += 1;
3.2.2 更新可能是异步
- setState 只在合成事件和⽣命周期中是“异步”的,在原⽣事件和 setTimeout 中都是同步的:
setState的“异步”并不是说内部由异步代码实现,其实本身执⾏的过程和代码都是同步的, 只是合成事件和钩⼦函数的调⽤顺序在更新之前,导致在合成事件和钩⼦函数中没法⽴⻢拿到更新后的值,形式了所谓的“异步”, 当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果 - setState第一个参数可以是一个函数,这个函数的第一个参数可以拿到上一个state的值。setState的第二个参数可以是一个函数,这个函数的参数可以拿到setState改变状态之后的值
例1:
// 不生效,因为异步,第二次调用无法拿到第一次更新的值
this.setState({ counter: this.state.counter + 1 })
this.setState({ counter: this.state.counter + 1 })
this.setState({ counter: this.state.counter + 1 })
// 生效,第一个参数可以拿到上一个state的值
this.setState(prevState => ({
counter: prevState.counter + 1
}));
this.setState(prevState => ({
counter: prevState.counter + 1
}));
this.setState(prevState => ({
counter: prevState.counter + 1
}));
例2:
this.setState({
n: this.state.n + 1
}, () => {
//状态完成改变之后触发,该回调运行在render之后
//可以拿到第一个参数改变之后的值
console.log(this.state.n);
});
3.2.3 批量更新的优化
setState 的批量更新优化也是建⽴在“异步”(合成事件、钩⼦函数)之上的,在原⽣事件和setTimeout 中不会批量更新,在“异步”中如果对同⼀个值进⾏多setState , setState 的批量更新策略会对其进⾏覆盖,取最后⼀次的执⾏(如上例1 )。
如果是同时 setState 多个不同的值,在更新时会对其进⾏合并批量更新。
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments。
4. 生命周期
4.1 旧版生命周期(react<16.0.0)
import React, { Component } from "react";
export default class Lifecycle extends Component {
constructor(props) {
super(props);
// 常用于初始化状态
console.log("1.组件构造函数执行");
}
componentWillMount() {
// 此时可以访问状态和属性,可进行api调用等
console.log("2.组件将要挂载");
}
componentDidMount() {
// 组件已挂载,可进行状态更新操作
console.log("3.组件已挂载");
}
componentWillReceiveProps() {
// 父组件传递的属性有变化,做相应响应
console.log("4.将要接收属性传递");
}
shouldComponentUpdate() {
// 组件是否需要更新,需要返回布尔值结果,优化点
console.log("5.组件是否需要更新?");
return true;
}
componentWillUpdate() {
// 组件将要更新,可做更新统计
console.log("6.组件将要更新");
}
componentDidUpdate() {
// 组件更新
console.log("7.组件已更新");
}
componentWillUnmount() {
// 组件将要卸载, 可做清理工作
console.log("8.组件将要卸载");
}
render() {
console.log("组件渲染");
return <div>生命周期探究</div>;
}
}
4.2 新版生命周期
- 挂载阶段(Mount):组件第一次在DOM树中被渲染的过程
- 更新阶段(Update):组件状态发生变化,重新更新渲染的过程
- 卸载过程(Unmount):组件从DOM树中被移除的过程
4.3.1 挂载阶段
constructor
组件的构造函数,第一个被执行,若没有显示定义它,会有一个默认的构造函数,但是若显示定义了构造函数,必须在构造函数中执行super(props),否则无法在构造函数中拿到this。
consturctor中通常只做两件事:
- 初始化组件的state
- 给事件处理方法绑定this
constructor(props) {
super(props);
// 不要在构造函数中调用 setState,可以直接给 state 设置初始值
this.state = { counter: 0 }
this.handleClick = this.handleClick.bind(this)
}
getDerviedStateFromProps
这是个静态方法,不能在这个函数里使用this,有两个参数props和state,分别指接受到的新参数和当前组件的state对象。这个函数会返回一个对象来更新当前的state对象,如果不需要可以返回null。
// 当 props.counter 变化时,赋值给 state
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
if (props.counter !== state.counter) {
return {
counter: props.counter
}
}
return null
}
handleClick = () => {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
现在可以显示传入counter,但是如果想通过点击实现state.counter的增加,会发现值不会发生任何变化,一直保持props传进来的值。这是由于React16.4^版本中,setState和forceUpdate也会触发这个生命周期,所以当组件内部state变化后,就会重新走这个方法,同时把state赋值为props的值。解决:需要多加一个字段来记录之前的props值,这样就会解决上述问题,代码如下:
// 这里只列出需要变化的地方
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
// 增加一个 preCounter 来记录之前的 props 传来的值
preCounter: 0,
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
// 跟 state.preCounter 进行比较
if (props.counter !== state.preCounter) {
return {
counter: props.counter,
preCounter: props.counter
}
}
return null
}
handleClick = () => {
this.setState({
counter: this.state.counter + 1
})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
render
- 返回一个虚拟DOM,会被挂载到虚拟DOM树中,最终渲染到真实DOM中
- render可能不只运行一次,只要需要重新渲染,就会重新运行
- 严禁使用setState,因为可能会导致无限递归渲染
- 可以返回布尔值或null 控制组件不渲染任何内容
componentDidMount()
- 执行依赖于DOM的操作
- 发送网络请求(官方建议)
- 添加订阅消息(会在componentWillUnmount取消订阅)
如果在componentDidMount中调用setState,就会出发一次额外的渲染,多调用一次render函数。由于它是在浏览器刷新屏幕前执行的,所以用户是没有感知的,但是我们应该避免这样使用,这样会带来一定的性能问题,尽量在constructor中初始化state对象。
4.3.2 组件更新阶段
当组件的props改变了,或者组件内部调用了steState/forceUpdate,会触发更新重新渲染,这个过程可能会发生多次。这个过程会依次调用下面方法:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
shouldComponentUpdate
- 指示react是否要重新渲染该组件,通过返回true和fasle来指定
- 默认情况下,会直接返回true
可以比较 this.props 和 nextProps ,this.state 和 nextState 值是否变化,来确认返回 true 或者 false。当返回 false 时,组件的更新过程停止,后续的 render、componentDidUpdate 也不会被调用。
getSnapshotBeforeUpdate
//prevProps prevState:更新之前的props和state
getSnapshotBeforeUpdate(prevProps, prevState)
- 这个方法执行在rendr之后,componentDidUpdate之前(真实的DOM构建完成,但还未实际渲染到页面中)
- 该函数的返回值,会作为componentDidUpdate的第三个参数
- 在该函数中,通常用于实现一些附加的dom操作
componentDidUpdate
// prevProps: 更新前的props
// prevState: 更新前的state
// snapshot: getSnapshotBeforeUpdate()生命周期的返回值
componentDidUpdate(prevProps, prevState, snapshot){}
- 首次渲染不会执行此方法,更新后会被立即调用
- 往往在该函数中使用dom操作,改变元素(也可以在此进行网络请求,例如:当props未发生变化时,则不执行网络请求)
4.3.3 组件卸载阶段
componentWillUnmount
- 一个组件被卸载和销毁之前被调用
- 清除 timer,取消网络请求或清除
- 取消在 componentDidMount() 中创建的订阅等
- 不应该在使用setState,因为组件一旦卸载,就不会装载也不会渲染