React.Component
render
函数class
定义一个组件:
constructor
是可选的,我们通常在 constructor
中初始化一些数据this.state
中维护的就是我们组件内部的数据render()
方法是 class
组件中唯一必须实现的方法当
render
函数被调用时, 它会检查this.props
和this.state
的变化并返回以下类型之一
JSX
创建
会被 React
渲染为 DOM
节点, \
会被 React
渲染为自定义组件
还是
均为 React
元素render
方法可以返回多个元素DOM
子树中DOM
中会被渲染为文本节点函数组件是使用
function
来进行定义的函数, 只是这个函数会返回和类组件中render
函数一样的内容
很多的事物都有从创建到销毁的整个过程,这个过程称之为是生命周期
React组件也有自己的生命周期,了解组件的生命周期可以让我们在最合适的地方完成自己想要的功能
生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段:
Mount
),组件第一次在DOM树中被渲染的过程Update
),组件状态发生变化,重新更新渲染的过程Unmount
),组件从DOM树中被移除的过程React内部为了告诉我们当前处于哪些阶段,会对组件内部实现的预定函数进行回调,这些函数就是生命周期函数
componentDidMount
函数:组件已经挂载到DOM上时,就会回调componentDidUpdate
函数:组件已经发生了更新时,就会回调componentWillUnmount
函数:组件卸载及销毁之前,就会回调我们谈React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的
state
或不进行方法绑定,则不需要为 React
组件实现构造函数constructor
中通常只做两件事情:
this.state
赋值对象来初始化内部 statecomponentDidMount()
会在组件挂载后 ( 插入DOM树中 ) 立即调用componentDidMount()
中通常进行哪些操作?
componentDidUpdate()
会在更新后会被立即调用,首次渲染不会执行
componentWillUnmount
会在组件卸载及销毁之前调用
除了上面介绍的生命周期函数之外,还有一些不常用的生命周期函数
getDerivedStateFromProps
state
的值在任何时候都依赖于 props
时使用getSnapshotBeforeUpdate
App
可能使用了多个Header
组件,每个地方的Header
展示的内容不同,那么我们就需要使用者传递给 Header
一些数据,让其进行展示Main
组件中一次请求了 Banner
数据和 ProductList
数据,那么就需要传递给它们来进行展示React
项目中,组件之间通信是非常重要的环节父组件在展示子组件, 可能会传递一些数据给子组件
Flow
或者TypeScript
,那么直接就可以进行类型验证Flow
或者TypeScript
,也可以通过 prop-type
库来进行参数验证propTypes
来进行对 props
的验证, 首先: 导入 prop-types
propTypes.string.isRequired
props
, 我们希望有默认值使用: 类名.defaultProps = {}
// 1.导入prop-types来进行属性校验
import propTypes from 'prop-types'
// 2.类型校验: 使用prop-types来校验父组件传递的props类型是否符合预期
// 如果是类中可以: static propTypes = { 进行类型校验 }
ChildCpn.propTypes = {
// name属性是必传的
name: propTypes.string.isRequired,
age: propTypes.number,
height: propTypes.number,
}
// 3.默认值,父组件没有传递props时的默认值
ChildCpn.defaultProps = {
name: 'hali',
age: 21,
height: 1.77,
}
vue
中是通过自定义事件来完成的React
中同样还是通过 props
传递消息this
绑定问题// 父组件
render() {
return (
当前计数: {this.state.counter}
{/* 子传父: 让子组件来调用父组件中的方法 */}
{/* 操作步骤: 父组件传递一个回调函数,让子组件来进行调用 */}
this.increment()} />
)
}
increment() {
this.setState({
counter: this.state.counter + 1,
})
}
// 子组件
class Counter extends Component {
render() {// 调用父组件传递的函数
return
}
}
非父子组件数据的共享:
在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递
但是对于有一些场景:比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)
如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。
React
提供了一个API
:Context
Context
提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
Context
设计目的是为了共享那些对于一个组件树而言是**“全局”的数据**,例如当前认证的用户、主题或首选语言Provider
中读取到当前的context
值defaultValue
是组件在顶层查找过程中没有找到对应的Provider
,那么就使用默认值Provider React
组件,它允许消费组件订阅 context 的变化Provider
接收一个 value
属性,传递给消费组件Provider
可以和多个消费组件有对应关系Provider
也可以嵌套使用,里层的会覆盖外层的数据;Provider
的 value
值发生变化时,它内部的所有消费组件都会重新渲class
上的 contextType
属性会被重赋值为一个由 React.createContext()
创建的 Context
对象this.context
来消费最近 Context
上的那个值render
函数中React
组件也可以订阅到 context
变更。这能让你在 函数式组件 中完成订阅 contextcontext
值,返回一个 React
节点
slot
翻译为插槽: 在React
中没有插槽概念, 因为React
太灵活了, 那在React如何实现插槽功能呢?使用过
Vue
的朋友知道, 如果我们向一个组件中插入内容时, 可以在子组件中预留插槽, 内容由父组件决定
父组件在使用子组件时, 将需要展示的内容用子组件包裹起来
子组件中通过:
理解props.children[0]
来取得父组件要展示的内容前面我们讲过:
- render 函数中 return 的JSX 代码最终会转换成
React.createElement('tabName', 'config', children)
- 而第三个参数 children 就是: 咱们在元素中插入的子元素, 在React源码中将
ChildrenArr
放到了props
属性中的children
中, 所以说子组件可以通过props.children
来接收到父组件插入的子元素
在插入插槽内容时, 你会发现不能实现像Vue中的指定插槽插入内容(具名插槽)
在React中可以通过 属性(props) 来指定你要插入的内容, 然后在子组件中使用 props 取出指定的JSX元素插入到指定位置
Children
使用场景: 当只有一个默认内容时, 直接插入到子元素即可props
指定slot
使用场景: 当有多个插槽内容时, 使用 props
形式传递如果你已经有了一个 props 对象,你可以使用展开运算符
...
来在 JSX 中传递整个 props 对象
function Profile(props) {
return (
{/* 在 JSX 中传递整个 props 对象。以下两个组件是等价的 */}
)
}
前面通过Context
主要实现的是数据的共享,但是在开发中如果有跨组件之间的事件传递,应该如何操作呢?
events
来完成对应的操作我们可以通过npm或者yarn来安装events:yarn add events
events常用的API:
创建 EventEmitter
对象:eventBus
对象;
发出事件:eventBus.emit
(“事件名称”, 参数列表);
监听事件:eventBus.addListener
(“事件名称”, 监听函数);
移除事件:eventBus.removeListener
(“事件名称”, 监听函数);
// 1.创建全局事件总线
const eventBus = new EventEmitter()
// 2.发射事件
emitHomeEvent() {
eventBus.emit('sayHello', 'hello home', 123)
}
// 3.监听事件
componentDidMount() {
eventBus.addListener('sayHello', this.handleSayHelloListener)
}
// 4.卸载事件
componentWillUnmount() {
eventBus.removeListener('sayHello', this.handleSayHelloListener)
}
React的开发模式中,通常情况下不需要、也不建议直接操作DOM元素,但是某些特殊的情况,确实需要获取到DOM进行某些操作: 文本选择或媒体播放;触发强制动画;集成第三方 DOM 库;
如何创建refs
来获取对应的DOM
呢?目前有三种方式:
方式一:传入字符串
this.refs.传入的字符串
格式获取对应的元素方式二:传入一个对象
对象是通过 React.createRef()
方式创建出来的;
使用时获取到创建的对象其中有一个current
属性就是对应的元素
方式三:传入一个函数
该函数会在DOM被挂载时进行回调,这个函数会传入一个 元素对象,我们可以自己保存
使用时,直接拿到之前保存的元素对象即可
import React, { PureComponent, createRef } from 'react'
// ...
constructor(props) {
super(props)
this.titleRef = createRef()
this.titleEl = null
}
render() {
return (
{/* hello react
*/}
hello react
hello react
(this.titleEl = arg)}>hello react
)
}
changeText() {
// 1.通过refs来操作DOM,有三种方式
// 方式一: 字符串
this.refs.titleRef.innerHTML = 'hello jean'
// 方式二: 对象
this.titleRef.current.innerHTML = 'hello JavaScript'
// 方式三: 函数
this.titleEl.innerHTML = 'hello TypeScript'
}
ref
的值根据节点的类型而有所不同:
当 ref
属性用于 HTML
元素时,构造函数中使用 React.createRef()
创建的 ref
接收底层 DOM
元素作为其 current
属性
当 ref
属性用于自定义 class
组件时,ref
对象接收组件的挂载实例作为其 current
属性
你不能在函数组件上使用 ref 属性,因为他们没有实例
constructor(props) {
this.counterRef = createRef()
}
render() {
return (
)
}
// 通过ref来获取类组件对象
appIncrementCount() {
// 调用子组件方法
this.counterRef.current.increment()
}
函数式组件是没有实例的,所以无法通过ref获取他们的实例:
但是某些时候,我们可能想要获取函数式组件中的某个DOM元素;
这个时候我们可以通过React.forwardRef
,后面我们也会学习hooks
中如何使用ref;
state
在 HTML 中,表单元素(如、
而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState() 来更新
React
的state
成为“唯一数据源”;React
组件还控制着用户输入过程中表单发生的操作;React
以这种方式控制取值的表单输入元素就叫做“受控组件”;由于在表单元素上设置了 value
属性,因此显示的值将始终为this.state.value
,这使得 React 的 state 成为唯一数据源。
由于 handleUsernameChange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新
React推荐大多数情况下使用 受控组件 来处理表单数据:
一个受控组件中,表单数据是由 React 组件来管理的;
另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理;
如果要使用非受控组件中的数据,那么我们需要使用 ref 来从DOM节点中获取表单数据
什么是高阶组件呢?它和高阶函数非常相似,我们可以先来回顾一下什么是: 高阶函数
高阶函数的维基百科定义(至少满足以下条件之一):
接受一个或多个函数作为输入
输出一个函数
JavaScript
中比较常见的filter、map、reduce
都是高阶函数
那么什么是高阶组件呢?
我们可以进行如下的解析:
高阶组件并不是React API的一部分,它是基于React的组合特性而形成的设计模式
高阶组件在一些React第三方库中非常常见:
- 比如redux中的connect (后续会讲到)
- 比如react-router中的withRouter (后续会讲到)
在开发中,我们可能遇到这样的场景:
某些页面是必须用户登录成功才能进行进入;
如果用户没有登录成功,那么直接跳转到登录页面;
这个时候,我们就可以使用高阶组件来完成鉴权操作
我们会发现利用高阶组件可以针对某些React代码进行更加优雅的处理
利用高阶组件可以完成多个组件中的共同功能
其实早期的React有提供组件之间的一种复用方式是mixin
,**目前已经不再建议使用: **
当然,(高阶组件)HOC 也有自己的一些缺陷:
HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难
HOC可以劫持props
,在不遵守约定的情况下也可能造成冲突
Hooks
的出现,是开创性的,它解决了很多React之前的存在的问题
比如this指向问题、比如hoc的嵌套复杂度问题等等
ref
时讲过,ref
不能应用于函数式组件:
forwardRef高阶函数
id
为root
的DOM
元素上的)Portal
提供了一种将子节点渲染到存在于父组件之外的 DOM 节点
child
是任何可渲染的 React 子元素, 例如一个元素,字符串或 fragment;container
是一个DOM 元素ReactDOM.createPortal(child, container)
我们希望可以不渲染这样根元素div应该如何操作呢?
React还提供了Fragment
的短语法:
StrictMode
是一个用来突出显示应用程序中潜在问题的工具
可以为应用程序的任何部分启用严格模式:
不会对 Header 和 Footer 组件运行严格模式检查
但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查
到底检测什么呢?
识别不安全的生命周期
使用过时的ref API
使用废弃的findDOMNode
方法
findDOMNode
来获取DOM,不过已经不推荐使用了检查意外的副作用
这个组件的 constructor
会被调用两次
这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用
在生产环境中,是不会被调用两次的
检测过时的context API
早期的Context
是通过static
属性声明Context
对象属性,通过getChildContext
返回Context
对象等方式来使用Context
的
目前这种方式已经不推荐使用,大家可以自行学习了解一下它的用法