React有哪些特性
- JSX 语法
- 单向数据绑定
- 虚拟 DOM (diff原理)
- 声明式编程
- Component
JSX语法
JSX = Javascript + XML
JSX 是一种 Javascript 的语法扩展,即在 Javascript 里面写 XML,JSX 实际是一种语法糖,在使用过程中会被 babel 进行编译转化成 JS 代码。
单向数据绑定(可以和vue做一下对比)
React是单向数据流,采用单向数据绑定,而 Vue 2.x 也是单向数据流,但同时支持单向数据绑定和双向数据绑定。
所谓数据绑定,就是指View层和Model层之间的映射关系。
单向数据绑定: Model的更新会触发View的更新,而View的更新不会触发Model的更新,它们的作用是单向的。
双向数据绑定: Model的更新会触发View的更新,View的更新也会触发Model的更新,它们的作用是相互的。
React
在 React 中,View 层是不能直接修改 State,必须通过相应的 Actions 来进行操作,即通过调用 setState 方法对View的State 进行更新,State更新后会触发View的重新渲染。
Vue
- 单向数据绑定:使用
v-bind属性绑定、v-on事件绑定或插值形式{{data}}。 - 双向数据绑定:使用
v-model指令,用户对View的更改会直接同步到Model。
数据流
所谓数据流,是指组件之间的数据流动。
虽然 Vue有双向数据绑定,但 Vue父子组件之间数据传递,仍然遵循单向数据流,即父组件可以向子组件传递props,但是子组件不能修改父组件传递来的props,子组件只能通过事件通知父组件进行数据更改。
虚拟DOM
react和vue对虚拟DOM是一样的定义。
Virtual DOM是一棵以JavaScript对象作为基础的树,每一个节点可以将其称为VNode,用对象属性来描述节点,实际上它是一层对真实DOM的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说Virtual DOM就是一个Js对象,是更加轻量级的对DOM的描述,用以表示整个文档。
虚拟dom优点:
1.减少操作真实dom。任何页面的变化,都只使用 VNode 进行操作对比,只需要在最后一步挂载更新dom,不需要频繁操作dom,从而提高页面性能。
2.跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染。
diff原理
Vue和React都是通过引入Virtual DOM的概念,极大地避免无效的Dom操作,使页面的构建效率提到了极大的提升。
而diff算法就是更高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处
不同之处
- react 采用单指针从左向右进行遍历
- vue采用双指针,从两头向中间进行遍历
相同之处
都只进行同级比较,忽略了跨级操作;常见的现象就是对数组或者对象中的深层元素进行修改时,视图层监测不到其变化,故不会对dom进行更新,需调用一些方法来告诉视图层需要更新dom。react中是setstate,vue中是vue.set。
声明式编程
声明式编程区别于命令式编程,它的特点就是我告诉计算机做什么,但是没有告诉你怎么做。
一般在声明式编程中,没有for或者if语句,而是会用map等函数,声明接收的结果,让计算机去执行一遍就可以了。
Component
React 的组件可以定义为 class 或函数的形式。就是一个UI的单元。目的是将要展示的内容,分成多个独立部分,每一个这样的部分,就是一个组件。class 组件目前提供了更多的功能。 关于生命周期,在下方介绍。
React生命周期
生命周期概念一般是存在与类组件当中,函数式组件需要借助hook(useEffect)来实现。
在类组件中,有新旧两版生命周期。
-
旧版生命周期:react版本 < 16.0.0
-
新版生命周期:react版本 >= 16.0.0
借助尚硅谷的图来理解。
旧版生命周期
Mounting阶段:
- constructor 同一个组件对象只会运行一次
不能在第一次挂载到页面之前,调用setState
- componentWillMount 正常情况下,和构造函数一样,它只会运行一次
可以使用setState,但是为了避免bug,不允许使用,因为在某些特殊情况下,该函数可能被调用多次
功能有点鸡肋,新版生命周期取消该钩子函数。
- render
react最重要的步骤,
创建虚拟dom,进行diff算法,最终渲染到页面的真实DOM中。
render可能不止运行一次,只要需要重新渲染,就会重新运行,禁止在这里使用setState,可能导致无限递归。
- componentDidMount 可以使用setState,只会运行一次。
通常情况下,会将网络请求,启动计时器等一开始需要的操作,书写在该函数中。
Updating阶段:
-
componentWillReceiveProps(nextProps) 组件加载时不调用,组件接受新的props时调用(第一次不算),参数为新的属性对象。
新版生命周期里官方取消了该钩子。 -
shouldComponentUpdate(nextProps,nextState) 是性能优化点,指示react是否要重新渲染该组件,通过返回true和false来指定,默认返回true
组件加载时不调用,只有组件将要更新时才会调用,值被覆盖原来的值也会调用。
-
componentWillUpdate(nextProps,nextState) 组件更新时调用,可以setState。
新版生命周期里官方取消了该钩子 -
componentDidUpdate 组件更新完调用。 往往在该函数中使用dom操作,改变元素。
以下为Unmounting阶段:
- componentWillUnmount
组件即将卸载前调用,只调用一次。 通常在该函数中销毁一些组件依赖的资源,比如计时器
新版生命周期
从图中对比可以看出,React16弃用了三个生命周期钩子新增了两个生命周期钩子,弃用的三个生命周期钩子,在16之后使用要加上UNSAFE_前缀
弃用的三个生命周期:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
新增的两个生命周期:
- getDerivedStateFromProps
- getSnapshotBeforeUpdate
static getDerivedStateFromProps(nextProps, prevState)
这个钩子函数,在挂载,更新时都会被调用。从props中获取state,这个生命周期的功能实际上就是将传入的props映射到state上面。 该函数是静态函数,所以取不到 this。如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾。
getSnapshotBeforeUpdate()
在最近的更改被提交到DOM元素前,使得组件可以在更改之前获得当前值,此生命周期返回的任意值都会传给componentDidUpdate()。在最新的渲染数据提交给DOM前会立即调用,它让你在组件的数据可能要改变之前获取他们。
state 和 props 区别
state
组件的显示可以由数据状态和外部参数所决定,而数据状态就是 state,一般在 constructor 中初始化。
需要修改里面的值的状态需要通过调用 setState 来改变,从而达到更新组件内部数据的作用,并且重新调用组件 render 方法。
props
React 的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件。可以把 props 理解为从外部传入组件内部的数据。
react 具有单向数据流的特性,所以他的主要作用是从父组件向子组件中传递数据
props 除了可以传字符串,数字,还可以传递对象,数组甚至是回调函数
区别
- props 是外部传递给组件的,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化
- props 在组件内部是不可修改的(react是单向数据流),但 state 在组件内部可以进行修改
super() 和 super(props) 区别?
ES6中,extends实现类的继承,super 关键字实现调用父类,super 代替的是父类的构建函数,使用 super(name) 相当于调用 sup.prototype.constructor.call(this,name)。
子类必须在constructor()中调用super() 方法否则新建实例 就会报错,报错的原因是 子类是没有自己的this对象的,它只能继承父类的this对象,然后对其进行加工,而super()就是将父类中的this对象继承给子类的,没有super() 子类就得不到this对象。
如果使用了constructor就必须写super(), 这个是用来初始化this的,可以绑定事件到this上。 如果要在constructor中使用this.props,就必须给super添加参数 super(props)
注意,无论有没有constructor,在render中的this.props都是可以使用的,这是react自动附带的 如果没有用到constructor 是可以不写的,react会默认添加一个空的constroctor.
setState
一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是state。
当需要修改里面的值的状态需要通过调用setState来改变,从而达到更新组件内部数据的作用。感觉类似于Vue中的Vue.set()。
因为React并不像vue2中调用Object.defineProperty数据响应式或者Vue3调用Proxy监听数据的变化,必须通过setState方法来告知react组件state已经发生了改变。
setState 可以接受两个参数,第一个参数可以是一个对象或者是一个函数,都是用来更新 state。第二个参数是一个回调函数(相当于Vue中的$NextTick ),我们可以在这里拿到更新的 state。
在使用setState更新数据的时候,setState的更新类型分成:
- 异步更新:在组件生命周期或React合成事件中
- 同步更新:在setTimeout或者原生dom事件中(click)
设计异步更新原因是,如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进行批量更新。
React事件绑定的方式
react应用中,事件名都是用小驼峰格式进行书写,事件绑定方式有四种
- render方法中使用bind
- render方法中使用箭头函数
- constructor中bind
- 定义阶段使用箭头函数绑定
1、render方法中使用bind(在组件每次render渲染的时候,都会重新进行bind的操作,影响性能)
class App extends React.Component {
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>test</div>
)
}
}
2、render方法中使用箭头函数(通过ES6的上下文来将this的指向绑定给当前组件,同样再每一次render的时候都会生成新的方法,影响性能)
class App extends React.Component {
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={e => this.handleClick(e)}>test</div>
)
}
}
3、constructor中bind(在constructor中预先bind当前组件,可以避免在render操作中重复绑定)
class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick}>test</div>
)
}
}
4、定义阶段使用箭头函数绑定
class App extends React.Component {
constructor(props) {
super(props);
}
handleClick = () => {
console.log('this > ', this);
}
render() {
return (
<div onClick={this.handleClick}>test</div>
)
}
}
综合下来,第四种是最常用的,即写函数的时候使用箭头函数去操作。
react组件通信
类似于Vue,组件间通信的方式通常有以下几种:
- 父子组件通信
- 子父组件通信
- 跨级组件通信
- 非嵌套组件间通信
- redux
-
父子组件通信(父组件通过向子组件传递 props,子组件得到 props 后进行相应的处理。)
export default class App extends Component{ //父组件 render(){ return( <div> <Sub title = "你好" /> </div> ) } } export default Sub = (props) => { //子组件 return( <h1> { props.title } </h1> ) } -
子父组件通信(利用回调函数,可以实现子组件向父组件通信:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。)
export default Sub = (props) => { //子组件 const cb = (msg) => { //msg return () => { props.callback(msg) } } return( <div> <button onClick = { cb("你好") }>点击我</button> </div> ) } export default class App extends Component{ //父组件 callback=(msg)=>{ console.log(msg); //msg } render(){ return( <div> <Sub onClick = { this.callback} /> </div> ) } } -
跨级组件通信(使用 context 是一种可行的方式,context 相当于一个全局变量,是一个大容器,可以把要通信的内容放在这个容器中,不管嵌套有多深,都可以随意取用)
使用 context需要满足两个条件:
- 上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象
- 子组件要声明自己需要使用 context
export default class App extends Component{ static childContextTypes = { // 父组件声明自己支持 context color:PropTypes.string, callback:PropTypes.func, } getChildContext(){ // 父组件提供一个函数,用来返回相应的 context 对象 return{ color:"red", callback:this.callback.bind(this) } } callback(msg){ console.log(msg) } render(){ return( <div> <Sub></Sub> </div> ); } } export default class SubSub extends Component{ static contextTypes = { // 子组件声明自己需要使用 context color:PropTypes.string, callback:PropTypes.func, } render(){ const style = { color:this.context.color } const cb = (msg) => { return () => { this.context.callback(msg); } } return( <div style = { style }> <button onClick = { cb("hello!") }>点击我</button> </div> ); } }
- 非嵌套组件(需要借助一个帮手event)不展开叙述了
- redux(集中式管理)