React 复习
React的三个核心依赖库
使用React
需要引入上述的三个库,crossorigin
这个属性是可以获取到跨站脚本的错误信息。
关于React
为何要分为三个库主要是因为react-native
因为手机端的react-dom
会将React
渲染出来的虚拟对应到原生的控件上,在web
端就是生成真实的dom
。
为什么要引入babel
babel在前端,用途是非常的广泛。作为前端经常使用的代码转换器和编译器。可以对js
做兼容性处理,比如es6
转es5
。
默认React
的开发可以不需要babel
,但是我们要写一些html
模板就需要使用React.createElement
这个函数,但是要先一些嵌套的额标签的话,那是十分繁琐的。所以我们可以编写jsx
代码(JavaScript XML),babel
的作用就是将jsx
语法转换为React.createElement
写法。
比如:
左边我们可以按照html
的写法创建标签,而经过babel编译后最终为React.createElement
形式,可以看到该形式是较为复杂。
JSX语法
我们要编写jsx
语法需要将代码写在如下标签中,表示我们写的为jsx
代码。
jsx的书写规范
- 首先
jsx
顶层只能有一个根元素,我们需要在外层包裹一个div
,或者Fragment
标签。 - 如果我们要换行书写还需要在外层包裹一个
()
。 jsx
支持单标签但是要以/>
结尾。jsx
内部要混入变量使用{}
,其中也可以使用一些表达式。- 标签的属性如果与一些保留词冲突的话需要改写,比如样式的类名
class
需要使用className
,另外还有label
标签中的for
属性,要使用htmlFor
来代替。 - 内联样式,要用
style={{key:value}}
的形式去写。
{}中能展示的变量类型
可以展示的类型:string, number,Array
(会将每一项拼在一起成字符串)。
不能展示的类型:undefined,null,boolean
,一般对象不能不能作为jsx
的子类即对象不能直接放置在{}中。
在{}
不能展示的变量类型,如果需要展示的话可以转换变量类型或者调用toString()
等方法。
条件渲染
当逻辑判断很多的时候可以使用if else
来判断,简单的情况可以使用三目运算符或者交集运算符。
还可以根据性能要求使用display:none
或者操作dom
的方式来切换dom
显示。
事件绑定和this
jsx
中的dom
事件绑定就是在指定的dom
上以onXxxx
形式绑定事件
关于绑定事件函数中的this
指向问题。上述函数中的this
不做任何处理时默认指向undefined
的。
因为React
在处理绑定的事件函数内部通过 btnClick.apply(undefined,[arg])
来调用的,内部是无法获得该上下文的this
的。
解决方式1:显示绑定this
解决方式2:使用箭头函数来定义事件函数
解决方式3:在{}是用箭头函数返回事件函数
推荐使用第三种,因为传值的时候也不需要使用柯里化来定义事件函数了。
jsx本质
jsx
实际上就是React.createElement
的语法糖
通过createElement
创建出来的对象就是ReactElement
对象,最终形成一个虚拟DOM
react
渲染流程:
jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 真实的DOM
react native
渲染流程:
jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 原生的控件(UIButton/Button)
源码展示:
React的组件化
创建组件方式
React
有两种创建组件的方式:类式组件和函数式组件。
另外根据组件的其他概念,比如状态,职责,UI等,可分为:有状态组件,无状态组件,展示型组件和容器型组件,异步组件,高阶组件等。
类式组件
- 首先无论是类组件还是函数式组件,创建时的名称首字母必须大写,不然会被识别为
html
的内置标签。 - 类组件实现需要继承
React.component
或者React.pureComponent
这个父类组件。 - 类组件必须实现
render
函数,该函数也是唯一必须实现的。
官网现在推荐使用ES6的类定义语法来定义类式组件。
函数式组件
函数式组件就是使用function
函数表达式来定义组件,需要返回的内容结构和类组件中render
函数返回的结构相同。
相比类式组件,函数式组件有以下特点:
- 没有生命周期,类式组件有一套自己的生命周期函数。
- 没有
this
实例,类式组件的this
指向实例。 - 没有内部的状态(
state
),类式组件可以通过在this.state
中定义状态变量。
生命周期函数
React
组件的生命周期函数表示了组件从开始创建到渲染成功,到最后销毁这一系列的过程。我们可以通过这些生命周期函数来在不同的组件阶段实现各种功能。
函数式组件可以通过hooks
来模拟生命周期函数,这里我们主要谈类式组件的生命周期函数。
constructor
谈之前我们先谈下类式组件的构造函数constructor
。
我们使用constructor
一般用来初始化组件的state
,或者给事件绑定this
,或者父类的构造函数来保存props
。
上述的构造函数可以省去不写,因为默认不写时,React在执行生成实例时就会调用上述实现方式的构造函数。
如果不写构造函数,我们也可以将state
直接写在类式作用域中,调用方式不变:
但是还有一个属性props
,如果我们没有在构造函数中声明保存,这个属性我们也可以直接在render
获取。
props
是获取从父组件传递来的参数的属性,之所以render
函数能够获取到props
,是内部源码做了一层处理,将props
保存了下来。所以我们可以直接使用props
。
生命周期函数在React
的不同版本有所不同,本篇所讲的主要针对于React
版本大于16.13.1
。
旧版生命周期函数图解:
新版生命周期函数图解:
旧版的生命周期函数已经不推荐使用了,当然仍然可以使用。如果要使用的话需要在函数名前加上UNSAFE_
前缀。我们不加前缀的话React
会内部转发调用带前缀的生命周期函数,同时会报警告。
以下生命周期是过时的的:
UNSAFE_componentWillMount()
组件即将挂载成功。UNSAFE_componentWillReceiveProps()
在已挂载的组件接收新的props
之前被调用。UNSAFE_componentWillUpdate()
当组件收到新的props
或state
时。会在渲染之前调用该函数。
新版生命周期相对要复杂些:
componentDidMount
当我们的组件第一次挂载完毕(插入DOM树)后,就会执行该函数。我们可以在这个生命周期函数中执行一些初始化的事,比如:一些需要依赖DOM的操作(获取一些DOM元素属性),网络请求,一些事件的订阅(对应在componentWillUnmount
中取消订阅)。
componentDidUpdate
当组件更新完后会被立即执行,首次渲染不会执行该方法。一般我们是通过this.setState
或者render
函数调用来触发这个函数。该函数可以获取上一次的state
,props
和snapshot
(快照值)。可以比较是否改变来执行相应的操作。注意我们可以在该函数中调用this.setState
方法,但是要注意需要条件判断执行,否则会出现死循环。
componentWillUnmount
组件即将卸载的函数,可以在该函数中执行取消订阅,网络请求取消或者引用销毁等操作。我们可以手动调用ReactDOM.unmountComponentAtNode()
来卸载指定的节点
getDerivedStateFromProps
这个函数并不常用,他是定义在类上的静态方法需要加上类名或者static
。当state
的值在任何时候都是取决于props
就可以使用该函数。该方法返回一个对象来更新state
getSnapshotBeforeUpdate
在组件即将更改前执行,可以获取一些DOM信息,然后返回。我们可以通过componentDidUpdate
这个函数来接受返回值。
shouldComponentUpdate
React组件会根据这个函数返回的值来确定是否更新,默认的行为是state每次发生变化组件都会更新。
当 props
或 state
发生变化时,shouldComponentUpdate()
会在渲染执行之前被调用。返回值默认为 true
。这里注意如果调用forceUpdate
强制更新,会跳过该函数执行。关于该函数的其他知悉可以了解:[React性能优化]()
总结:
初始化阶段,由ReactDOM.render()触发---初次渲染
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
更新阶段,由组件内部
this.setSate()
或父组件重新render
触发static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate
componentDidUpdate()
卸载阶段:由内部
ReactDOM.unmountComponentAtNode()
触发componentWillUnmount
组件的通信方式
在组件中我们会经常遇到组件相互传值的方式,其中需要用到props
这个重要的属性。
父组件传递给子组件
父组件通过在子组件标签上以 属性=值 方式传递数据给子组件。
父组件:
类子组件接收:
函数组件接收:
如果使用过Vue
,我们给子组件传递值的时候,子组件可以对传递过来的props
进行验证。React
也可以对传递过来的props
进行验证,但是React
自身并不能验证,我们需要借助第三方库或者TS来进行验证。这里第三方库推荐prop-types
。
使用方式:
子组件传递给父组件
在Vue
中子传递父是通过自定义事件实现,在React
中就还是通过Props
实现的,我们可以向子组件传递一个函数,让子组件执行来实现子传父。
非父子组件数据的共享
有时候我们需要跨多个层级进行数据共享,但是一直通过props
逐层传递就会出现中间的冗余。
我们可以使用React
的Context
方法。
Context
是为了共享那些对于一个组件树而言是"全局"的数据,例如当前认证的用户、主题或首选语言等。
我们首先需要创建一个共享的Context
对象,我们可以在createContext()函数中传入一个对象设置默认值。
当组件所处的树中没有匹配到 Provider
时,其 defaultValue
参数才会生效。此默认值有助于在不使用 Provider
包装组件的情况下对组件进行测试。注意:将 undefined
传递给 Provider
的 value
时,消费组件的 defaultValue
不会生效。
后代元素接收数据,类和函数组件都可以通过Consumer
方式获取。
另一种获取数据方式,只有类组件可以使用:
事件总线
事件总线其实就是利用发布订阅方式进行跨组件传递,这里推荐两个库:events
和pubsub-js
,这两个库都可以用作传递数据,这里就以events来做案例。
安装方式:
使用方式:
React中实现vue的slot
React
中没有像Vue
中提供的slot
插槽相关的API,但是灵活的JSX属性可以实现类似的功能。
方式1:
卸载组件中的标签会以props
的形式传递给组件,组件中获取方式如下:
这种方式可以实现slot
的效果,但是还是不够灵活,组件内必须按照固定的位置来渲染组件。
方式2:
通过属性传值方式就可以在外部控制指定位置的插槽了,组件中接收方式如下:
这种方式在外部就可以控制插槽的位置,类似于vue
中的具名插槽。
PureComponent
当我们在state
中添加一个属性,但是我们并没有在jsx
中使用这个属性,即视图并没有依赖这个数据,我们就不用更新视图,但是React
只要监听到state
的改变就会重新调用render
。另外如果子元素也没有使用父元素的state
那么也不用更新,但是只要父元素执行了render
函数,其子元素的所有的render
函数都会执行,这些更新是完全没有必要。我们可以通过shouldComponentUpdate
来控制组件的更新。
该函数默认返回true
,即需要调用render
函数,我们可以比较新旧props
来判断是否需要更新,即返回false
就不更新。当然React
是想到了这个问题,所以提供了一个组件PureComponent
。我们书写组件如果需要判断是否更新就可以继承这个组件
该组件重写了shouldComponentUpdate
这个方法,默认对新旧的state
或者props
进行浅比较,判断是否更新。所以我们不需要自己实现比较函数,当然React
也不推荐我们自行实现深比较函数,因为深比较也会消耗性能,这是不必要的。
React
通过该比较函数判断是否需要更新
高阶组件memo
我们之前处理组件更新问题对于类组件时可以使用PureCompoent
,但是对于函数式组件我们就可以使用memo
这个高阶组件来处理。
我们可以使用这个高阶组件来包裹一个函数式组件返回一个组件。
这样该函数就和继承了PureComponent
的类式组件一样了。
ref
React
的原生开发中也不建议直接操作DOM,但有时我们确实获取DOM可以通过ref
方式。
在React
中使用ref
有三种方式:
字符串方式
这种方式已经过时,官方不推荐使用了
对象形式
首先创建一个ref
对象
获取方式:
函数形式
传入一个回调函数,这个函数会传入一个元素对象,我们定义属性接收就可以。
推荐方式二三都可以,但是要注意上述方式都只适用于类组件,函数组件需要通过React.forwardRef
方式获取。
ref的转发
之前说的方式都只适用于类式组件,而函数时组件可以通过React中自带的一个高阶函数(forwardRef
)处理来使用ref
。
运行结果:
受控组件和非受控组件
在HTML中表单元素(input
,textarea
,select
等)通常自己维护state
,并且根据用户输入进行更新。而在React
中可变状态通常保存在state
中来进行更新维护。我们可以把两者结合起来,使 React
的 state
成为"唯一数据源"。渲染表单的 React
组件还控制着用户输入过程中表单发生的操作。被 React
以这种方式控制取值的表单输入元素就叫做"受控组件"。其实可以理解为vue中对于表单数据的双向绑定(v-model)
。
当然表单数据将交由 DOM 节点本身来处理就称为非受控组件。一般还是推荐使用受控组件。
Portals的使用
我们所写的DOM元素都是挂载在id
为root
的根元素上,但是有些DOM结构我们希望它独立的挂载到父元素或者根元素以外的地方,那么就可以使用Portals
这个API。
在父组件中我们希望将Modal
这个组件中的子元素渲染到指定id的元素内。
我们可以在Modal
组件中使用这个api
public/index.html
中设置
fragment的使用
我们在书写jsx
语法是,返回的DOM元素是必须要一个跟标签包裹,比如div
,但是这个div
除了满足语法外没有任何的作用,所以使用fragment
就可以解决这个问题。
这个标签使用React内部自带
该标签并不会被渲染成任何元素,如果不加key
属性可以简写为<></>
,如果加上key
就需要完整的写法。
StrictMode
这个组件不会被渲任何元素,主要作用为检查子元素的潜在问题。类似JS的严格模式。
主要检测内部的子元素是否使用了不安全的生命周期,过时的refAPI
和一些废弃的方法和API。还可以检测意外的副作用,比如组件的constructor
就会执行两次来确定是否产生副作用。
Comment here is closed