React
React 组件化开发
什么是组件化开发?
- 组件化开发是一种分而治之的思想:
- 口 如果我们将 一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂 ,而且不利于后续的管理以及扩展。
- 口 但如果,我们将 一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
- 我们需要通过组件化的思想来思考整个应用程序:
- 口 我们将一个完整的页面分成很多个组件;
- 口 每个组件都用于实现页面的一个功能块;
口 而每一个组件又可以进行细分;
口 而组件本身又可以在多个地方进行复用;
React的组件化
- 组件化是React的核心思想,也是我们后续课程的重点,前面我们封装的App本身就是一个组件:
- 口 组件化提供了一种抽象,让我们可以开发出一个个独立可复的小组件来构造我们的应用。
- 口 任何的应用都会被抽象成一颗组件树。
- 组件化思想的应用:
- 口 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 口 尽可能的将页面拆分成一个个小的、可复用的组件。
- 口这样让我们的代码更加方便组织和管理,并且扩展性也更强。
- React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:
- 口 根据组件的定义方式,可以分为:函数组件(Functional Component) 和 类组件(Class Component);
- 口 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component) 和 有状态组件(Stateful Component);
- 口 根据组件的不同职责,可以分成:展示型组件(Presentational Component) 和 容器型组件(Container Component);
- 这些概念有很多重委,但是他们最主要是关注数据逻辑和UI展示的分离:
- 口 函数组件、无状态组件、展示型组件 主要关注UI的展示;
- 口 类組件、有状态组件、容器型组件 主要关注数据逻辑;
- 当然还有很多组件的其他概念:比如异步组件、高阶组件等,我们后续再学习。
类组件
- 类组件的定义有如下要求:
- 口 组件的名称是大写字符开头(无论类组件还是函数组件)
- 口 类组件需要继承自 React.Component
- 类组件必须实现render函数
- 在ES6之前,可以通过create-react-class 模块来定义类组件,但是目前官网建议我们使用ES6的class类定义。
- 使用class定义一个组件:
- 口 constructor是可选的,我们通常在constructor中初始化一些数据;
- 口 this.state中维护的就是我们组件内部的数据;
- 口 render() 方法是 class 组件中唯一必须实现的方法;
render()函数的返回值
当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:
React 元素:
- 口 通常通过JSX 创建。
- 口 例如,
<div />
会被 React 渲染为 DOM 节点,<MyComponent />
会被 React 渲染为自定义组件;- 口 无论是
<div />
还是<MyComponent />
均为 React 元素。
数组或 fragments:使得 render 方法可以返回多个元素。
Portals: 可以渲染子节点到不同的 DOM 子树中。
-字符串或数值类型:它们在 DOM 中会被渲染为文本节点
布尔类型或 null:什么都不渲染。
函数组件
- 函数组件是 使用function来进行定义的函数,只是 这个函数会返回和类组件中render函数返回一样的内容。
- 函数组件有自己的特点(当然,后面我们会讲hooks,就不一样了):
- 口 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
- 口 this关键字不能指向组件实例(因为没有组件实例);
- 口 没有内部状态 (state)
- 我们来定义一个函数组件:
export default function App(){
return <div>HelLo World</div>
}
- 在前面的学习中,我们主要讲解类组件,后面学习Hooks时,会针对函数式组件进行更多的学习。
React 组件生命周期
认识生命周期
-
很多的事物都有从创建到销毁的整个过程,这个过程称之为是生命周期:
-
React组件也有自己的生命周期,了解组件的生命周期可以让我们在最合适的地方完成自己想要的功能:
-
生命周期和生命周期函数的关系:
-
生命周期 是一个 抽象的概念,在生命周期的整个过程,分成了很多个阶段;
- 比如装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
- 比如更新过程(Update),组件状态发生变化,重新更新涫染的过程;
- 比如卸载过程(Unmount),组件从DOM树中被移除的过程;
-
React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:
- 口 比如实现componentDidMount函数:組件己经挂载到DOM上时,就会回调;
- 口 比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调;
- 口 比如实现componentwilUnmount函数:组件即将被移除时,就会回调;
- 口 我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能;
-
我们谈到React生命周期时,主要谈的是类的生命周期,因为函数式组件没有生命周期。(后面可以通过hooks模拟生命周期的回调)
生命周期解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qu6AHfUh-1687143660083)(file:///Volumes/web/0%E9%9D%A2%E8%AF%952023/%E9%A1%B9%E7%9B%AE%E6%88%AA%E5%9B%BE/%E6%88%AA%E5%B1%8F2023-06-15%2012.00.28.png)]
生命周期函数
Constructor
- 如果不初始化 state或不进行方法绑定,则不需要为React组件实现构造函数。
- constructor中通常只做两件事;
- 通过给this.state赋值对象来初始化内部的state;
- 为事件绑定实例 this;
componentDidMount
- componentDidMount()会在组件挂载后(插入DOM树中)立即调用;
- componentDidMount()中通常进行哪些操作呢?
- 依赖于DOM的操作可以在这里进行;
- 在此处发送网络请求就是最好的地方;(官方建议)
- 可以在此处添加一些订阅(会在componentWillUnmount取消订阅);
componentDidUpdate
- componentDidUpdate() 会在更新后会被立即调用,首次渲染不会执行此方法。
-口 当组件更新后,可以在此处对 DOM 进行操作;
-口 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;
(例如,当 props 未发生变化时,则不会执行网 络请求)。
componentWillUnmount
- componentwilUnmount()会在组件卸载及销段之前直接调用。
- 口在此方法中执行必要的清理操作;
- 口例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等
不常用的生命周期函数
- 除了上面介绍的生命周期西数之外,还有一些不常用 的生命周期函数:
- getDerivedStateFromProps: state 的值在任何时候都依赖于props时使用;该方法返回一个对象来更新state;
- getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位買) - - shouldComponentUpdate:该生命周期函数很常用,但是我们等待讲性能优化时再来详细讲解;
组件开发嵌套关系
认识组件的嵌套
- 组组件之间存在嵌套关系:
- 口 在之前的案例中,我们只是创建了一个组件App;
- 口 如果我们一个应用程序将所有的逻辑都放在一个组件中,那么这个组件就会变成非常的臃肿和难以维护;
- 口 所以组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件;
- 口 再将这些组件组合嵌套在一起,最终形成我们的应用程序;
React 组件间通信
认识组件间的通信
- 在开发过程中,我们经常会遇到需要组件之间相互通信:
- 比如:App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示;
- 又比如:我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;
- 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
- 总之,在一个React项目中,组件之间的通信是非常重要的环节;
父组件传递子组件 - 类组件和函数组件
- 父组件在展示子组件,可能会传递一些数据给子组件:
- 父组件通过 属性=值 的形式来传递给子组件数据;
- 子组件通过 props 参数获取父组件传递过来的数据;
参数propTypes
-
对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说:
- 当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证;
- 但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证;
- 从 React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库
-
更多的验证方式,可以参考官网:https//zh-hans.reactis.org/docs/typeche,king-with-proptypes.htm
- 口 比如验证数组,并且数组中包含哪些元素;
- 口 比如验证对象,并且对象中包含哪些key以及value是什么类型;
- 口 比如某个原生是必须的,使用 requiredFunc: Prop Types.func.isRequired
-
如果没有传递,我们希望有默认值
- 使用defaultProps就可以了
ChildCpn.propTypes = {
name:PropTypes.string,
age:PropTypes.number,
height:PropTypes.number
}
子组件传递父组件
-
某些情况,我们也需要子组件向父组件传递消息:
- 口 在vue中是通过自定义事件来完成的;
- 口 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可。
-
我们这里来完成一个案例:
- 口 将计数器案例进行拆解;
- 口 将按钮封装到子组件中:CounterButton;
- 口 CounterButton发生点击事件,将内容传递到父组件中,修改counter的值
// 父组件
import React, { Component } from 'react';
import CounterButton from './CounterButton';
import SubCounter from './SubCounter';
class App extends Component {
constructor() {
super();
this.state = {
counter: 0,
};
}
counterClick(count) {
this.setState({
counter: this.state.counter + count,
});
}
render() {
const { counter } = this.state;
return (
<div>
<h1>05_组件通信-子传父</h1>
<h2>当前计数:{counter}</h2>
<CounterButton addClick={(count) => this.counterClick(count)}></CounterButton>
<SubCounter subClick={(count) => this.counterClick(count)}></SubCounter>
</div>
);
}
}
export default App;
// 子组件
import React, { Component } from 'react';
class CounterButton extends Component {
addCount(count) {
this.props.addClick(count);
}
render() {
return (
<div>
<button onClick={(e) => this.addCount(1)}>+1</button>
<button onClick={(e) => this.addCount(5)}>+5</button>
<button onClick={(e) => this.addCount(10)}>+10</button>
</div>
);
}
}
export default CounterButton;
// 子组件
import React, { Component } from 'react';
class SubCounter extends Component {
subCount(count) {
this.props.subClick(count);
}
render() {
return (
<div>
<button onClick={(e) => this.subCount(-1)}>-1</button>
<button onClick={(e) => this.subCount(-5)}>-5</button>
<button onClick={(e) => this.subCount(-10)}>-10</button>
</div>
);
}
}
export default SubCounter;
组件通信案例
- tab切换
React 组件插槽用法
React中的插槽(slot)
-
在开发中,我们抽取了一个组件,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素。
-
我们应该让使用者可以决定某一块区域到底存放什么内容。
-
这种需求在Vue当中有一个固定的做法是通过slot来完成的,React呢?
-
React对于这种需要插槽的情况非常灵活,有两种方案可以实现:
- 口 组件的 children 子元素;
- 口 props属性 传递React元素;
children实现插槽
-
每个组件都可以获取到 props.children:它包含组件的开始标签和结束标签之间的内容。
-
只传入一个元素时,children就是当前元素
-
传入多个元素时,children为数组
class App extends Component {
render() {
return (
<div>
<h1>07_组件的插槽</h1>
<NavBar>
<button>返回</button>
<h1>标题</h1>
<button>更多</button>
</NavBar>
</div>
);
}
}
class index extends Component {
render() {
const { children } = this.props;
return (
<div className="navbar">
<div className="left">{children[0]}</div>
<div className="center">{children[1]}</div>
<div className="right">{children[2]}</div>
</div>
);
}
}
props实现插槽
- 通过children实现的方案虽然可行,但是有一个弊端:
- 通过索引值获取传入的元素很容易出错,不能精准的获取传入的原生;
- 另外一个种方案就是使用 props 实现:
- 口 通过具体的属性名,可以让我们在传入和获取时更加的精准
<NavBarV2
leftSlot={<button>返回2</button>}
centerSlot={<h1>标题2</h1>}
rightSlot={<button>...</button>}
></NavBarV2>
class index extends Component {
render() {
const { leftSlot, centerSlot, rightSlot } = this.props;
return (
<div className="navbar">
<div className="left">{leftSlot}</div>
<div className="center">{centerSlot}</div>
<div className="right">{rightSlot}</div>
</div>
);
}
}
作用域插槽
React 非父子通信
Context应用场景
-
非父子组件数据的共享:
-
口 在开发中,比较常见的数据传递方式是通过props属性自上而下(由父到子)进行传递。
-
口 但是对于有一些场景: 比如一些数据需要在多个组件中进行共享(地区偏好、UI主题、用户登录状态、用户信息等)。
-
口 如果我们在顶层的App中定义这些信息,之后一层层传递下去,那么对于一些中间层不需要数据的组件来说,是一种冗余的操作。
-
我们实现一个一层层传递的案例:
- 口 我这边顺便补充一个小的知识点:Spread Attributes
-
但是,如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:
- 口 React提供了一个APl:Contex[https://zh-hans.react.dev/reference/react/useContext];
- 口 Context 提供了一种 在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props;
- 口 Context 设计目的是为了 共享那些对于一个组件树而言是“全局〞的数据,例如:当前认证的用户、主题或首选语言
Context相关API
-
React.createContext
- 创建一个需要共享的Context对象;
- 如果一个组件订阅了Context,那么从这个组件会从离自身最近的那个匹配的Provider中读取到当前的context的值;
- defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值;
const myContext = React.createContext(defaultValue)
-
Context.Provider
- 口 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
- 口 Provider 接收一个 value 属性,传递给消费组件;
- 一个 Provider 可以和多个消费组件有对应关系;
- 口 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
- 口 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;
-
Class.contextType
- 口 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
- 口 这能让你使用 this.context 来消费最近 Context 上的那个值;
- 口 你可以在任何生命周期中访问到它,包括 render 函数中;
myClass.contextType = myContext;
-
Context.Consumer
- 口 这里,React 组件也可以订阅到 context 变更。
- 口 这能让你在函数式组件中完成订阅 context.
- 口 这里需要函数作为子元素(function as child) 这种做法;
- 口 这个函数接收当前的 context 值,返回一个 React 节点;
-
什么时候使用Context.Consumer呢?
- 口 1.当使用value的组件是一个函数式组件时;
- 口 2.当组件中需要使用多个Context时;
React 非父子通信 - 事件总线event-bus
npm install hy-event-bus
setState使用详解
为什么使用setState
-
开发中我们井不能直接通过修改state的值来让界面发生更新:
- 口 因为我们修改 state 之后,希望 React根据最新的State来渲染组件,但是这种方式的修改React并不知道数据发生变变
- 口 React井没有实现类似于Vue 中的Object.defineProperty 或者Vue3中的Proxy的的方式来监听数据的交化;
- 口 我们必须通过 setState 来告知React数据已经发生了交化;
-
?疑惑:在组件中井没有实现setState的方法,为什么可以调用呢?
- 口 原因很简单,setState方法是从Component中继承过来的,
Component.prototype.setState = function (partialState, callback) {
if (
typeof partialState !== 'object' &&
typeof partialState !== 'function' &&
partialState != null
) {
throw new Error(
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
}
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
setState的三种用法
-
- setState更多用法
-
- 基本使用
Object.assign(state,setState)
属性名相同的值会覆盖掉
changeText() { this.setState({ msg: 'new world!!!', }); }
- 基本使用
-
2.setState可以传入一个回调函数
- 好处: 1. 可以在回调函数中 编写新的 state的逻辑
- 好处: 2. 当前的回调函数会将之前的state和props传递进来
this.setState((state, props) => { // 1. 编写一些对新的state处理逻辑 // 2. 可以获取之前的 state和props值 console.log(state); return { msg: 'setState传入一个回调函数', }; });
-
- setState 在React的事件处理中是一个异步调用
- 如果希望在数据更新之后(数据合并),获取到对应的结果执行一些逻辑代码,
- 那么可以在setState中传入第二个参数: callback
this.setState({ msg: 'new world!!!' }, () => { console.log('++++++++', this.state.msg); }); console.log('---------', this.state.msg);
setState 异步更新
-
setState的更新是异步的?
- 最终打印结果是hello world;
- 可见setState是异步的操作,我们并不能在执行完setState之后立马拿到最新的state的结果。
changeText() { this.setState({ msg: 'new world!!!' }, () => { console.log('++++++++', this.state.msg); //new world!!! }); console.log('---------', this.state.msg); // hello world }
-
为什么 setState 设计成为异步呢?
- setState 设计为异步,可以 显著的提升性能
- 如果每次调用 setState 都进行一次更新,那么意味着 render 函数会被频繁调用,界面重新渲染,这样效率很低
- 最好的办法应该是: 获取到多个更新,之后进行批量更新;
- 如果同步更新了state,但还是没有执行render函数,那么state和props不能保持更新
- state和props不能保持一致性,会在开发中产生很多问题
- setState 设计为异步,可以 显著的提升性能
-
如何获取异步更新的结果?
-
方式一: setState 的回调
- setState接收两个参数: 第二个参数是第一个参数的回调函数,这个回调函数会在更新后执行;
setState(partialState,callback)
-
方式二:在生命周期函数
componentDidUpdate(prevProps, provState, snapshot) { console.log(this.state.msg); }
React 组件化开发 二
React性能优化SCU
React更新机制
- React渲染流程
JSX -> 虚拟DOM -> 真实DOM - React的更新流程
-
React在props或state发送改变时,会调用React的render方法,会创建一颗不同的树。
-
React如果要基于这两颗不同的树之间的差别来判断如何有效的更新UI
- 如果一颗树参考另外一颗树进行完全比较更新,那么即使时最先进的算法,该算法的复杂度为O(n^2),其中n是树中元素的数量;
- https://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf;
- 如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算将在十亿的量级范围;
- 这个开销太过昂贵,React 的更新性能就会变得非常低效;
-
于是,React 对这个算法进行了优化,将其优化成了O(n),
-
如何优化?
- 同层节点之间相互比较,不会垮节点比较;
- 不同类型的节点,产生不同的树结构;
- 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;
-
keys的优化
- 遍历列表时,总会有提示一个警告,让我们加入一个key属性:
- 方式一:在最后位置插入数据
- 这种情况,有无key意义不大
- 方式二:在前面插入数据
- 这种做法,在没有key的情况下,所有的li都需要进行修改;
- 当子元素(这里是li)拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素:
- 在下面这种场景下,key为111和222的元素仅仅进行位移,不需要进行任何的修改;
- 将key为333的元素插入到最前面的位置即可;
- key的注意事项:
- key应该是唯一的;
- key不要使用随机数(随机数在下一次render时,会重新生成一个数字)
- 使用index作为key,对性能是没有优化的;
render函数被调用
-
我们之前的一个嵌套案例
- 在App中,我们增加了一个计数器的代码;
- 当单击+1时,会重新调用App的render函数;
- 而当App的render函数被调用时,所有的子组件的render函数都会被重新调用;
-
在开发中,我们只要修改了App中的数据,所有组件都需要重新rednder,进行diff算法,性能必然很低:
- 事实上,很多组件没有必要重新render
- 他们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法;
-
如何来控制render方法是否被调用呢?
- 通过
shouldComponentUpdate
方法即可;
- 通过
shouldComponentUpdate
- React 给我们提供了一个生命周期方法 shouldComponentUpdate (很多时候,我们简称为SCU),这个方法接受参数,并且需要有返回值:
- 该方法有两个参数:
- 参数一: nextProps修改之后,最新的props属性
- 参数二:nextState 修改之后,最新的state属性
- 该方法返回值是一个boolean类型:
- 返回值为true,那么就需要调用render方法;
- 返回值为false,那么就不需要调用render方法;
- 默认返回的是true,也就是只要state发送改变,就会调用render方法;
PureComponent
- 如果所有的类,我们都需要手动来实现 shouldComponentUpdate,那么会给开发增加很多的工作量。
- shouldComponentUpdate中的各种判断目的是:
- props或者state中的数据是否发生了变化,来决定 shouldComponentUpdate 返回值 是 true或false;
- 将class继承自 PureComponent
shallowEqual方法
- shallowEqual浅层比较
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
高阶组件 memo
- 类组件可以使用 PureComponent,那么函数式组件呢?
- 事实上,函数式组件,在props没有改变时,也不希望重新渲染DOM树结构
const Profile = memo(function (props) {
console.log('profile render');
return (
<div>
<h3>Profile:{props.msg}</h3>
</div>
);
});
export default Profile;
不可变数据的力量
获取DOM方式refs
如何使用Ref
-
在React开发中,通常情况下不需要、也不建议直接操作原生DOM,但是某些特殊的情况,确实需要获取到DOM进行某些操作:
- 管理焦点,文本选择或媒体播放 ;
- 触发强制动画 ;
- 集成第三方DOM库;
- 可以通过refs获取DOM
-
ref的类型
- ref 的值根据节点的类型而有所不同:
- 口 当ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref接收底层 DOM 元素作为其 current 属性;
- 口 当ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性;
- 口 你不能在函数组件上使用 ref 属性,因为他们没有实例;
- 函数式组件是没有实例的,所以无法通过ref获取他们的实例:
- 口 但是某些时候,我们可能想要获取函数式组件中的某个DOM元素;
- 口这个时候我们可以通过 React.forwardRef,后面我们也会学习 hooks 中如何使用ref;
- ref 的值根据节点的类型而有所不同:
-
React中,获取DOM
-
- 在React元素上绑定一个ref字符串
-
- 提前创建好 ref 对象,createRef(),将创建出来的对象绑定到元素上
-
- 传入一个回调函数,在对应的元素被渲染之后,回调函数被执行,并且将元素传入
-
export class App extends PureComponent {
constructor() {
super();
this.state = {};
this.titleRef = createRef();
this.titleEl = null;
}
getNativeDom() {
// 1. 在React元素上绑定一个ref字符串
// console.log(this.refs.why);
// 2. 提前创建好 ref 对象,createRef(),将创建出来的对象绑定到元素上
// console.log(this.titleRef.current);
// 3.传入一个回调函数,在对应的元素被渲染之后,回调函数被执行,并且将元素传入
console.log(this.titleEl);
}
render() {
return (
<div>
<h1>14_ref获取DOM</h1>
<h2 ref="why">hello world</h2>
<h2 ref={this.titleRef}>titleRef</h2>
<h2 ref={(el) => (this.titleEl = el)}>传入一个回调函数</h2>
<button onClick={(e) => this.getNativeDom()}>获DOM</button>
</div>
);
}
}
- React中,获取组件
-
类组件
- 提前创建好 ref 对象,createRef(),将创建出来的对象绑定到元素上
this.hwRef = createRef(); ...... getComponennt() { console.log(this.hwRef.current); this.hwRef.current.test(); } <HelloWorldFunc ref={this.hwRef}></HelloWorldFunc> <button onClick={(e) => this.getComponennt()}>获组件实例对象</button>
-
函数式组件
-
ref转发
-
ref不能应用于函数式组件
-
在开发中,想要获取函数式组件中某个元素的DOM
- 方式一:直接传入ref属性(❌)
- 方式二:通过 forwardRef 高阶函数
const HelloWorldFunc = forwardRef(function (props, ref) { return ( <div> <h3 ref={ref}>HelloWorldFunc</h3> <h4>hhh</h4> </div> ); });
-
-
受控和非受控组件
认识受控组件
- 在React中,HTML表单的处理方式和普通的DOM元素不太一样:
- 表单元素通常会保存在一些内部的state。
- 比如:下面的html表单元素
- 这个处理方是DOM默认处理html表单的行为,在 用户点击提交时提交到某个服务器 中,并且 刷新页面 ;
- 在 React 中, 并没有禁止这个行为,它依然有效的;
- 但是,通常情况下会使用JS函数来方便的处理表单提交,同时 还可以访问用户填写的表单数据;
- 实现这种效果的标准方式是 使用“受控组件”;
受控组件基本演练
-
在HTML中,表单元素(如:
<input>
、<textarea>
、<select>
)之类的表单元素通常自己维护state,并根据用户输入进行更新。 -
但,在React中,可变控件(mutable state)通常保存在组件的 __state属性__中,并且 只能通过使用 setState()来更新。
- 两者结合,使 React 的state成为“唯一数据源”;
- 渲染表单的 React组件还控制着用户输入过程中表单发生的操作;
- 被 __React以这种方式控制取值的表单输入元素__就叫做 “受控组件”;
-
由于,在表单元素上设置了 value属性,因此,显示的值始终为
this.state.value
,这使得 React 的state成为唯一数据源。 -
由于, handleUsernameChange 在每次按键时都会执行并更新 react的state,因此显示的值将随着用户输入而更新。
非受控组件
-
React 推荐大多数情况下,使用 受控组件 来处理表单数据;
- 一个 __受控组件__中,表单数据时 由React组件来管理的;
- 另一种替代方案是 使用非受控组件 ,这时表单数据将 __交由DOM节点__来处理;
-
如果要使用非受控组件中的数据,那么需要使用 ref 来从DOM节点中获取表单数据。
<input ref={this.introRef} type="text" defaultValue={intro} />
React高阶组件
认识高阶函数
- 什么是高阶组件?
- 与高阶函数相似
- 高阶组件:Higher-Order Components, 简称 HOC
- 官方定义:高阶组件是参数为组件,返回值为新组件的 函数
- 高阶函数?
- 至少满足以下条件之一:
- 接受一个或者多个函数作为输入;
- 输出一个函数;
- 至少满足以下条件之一:
定义
const EnhancedComponent = higherOrderComponent(WrapperComponent)
function hoc(WrapperComponent) {
// 1. 定义一个类组件
class NewWrapperComponent extends PureComponent {
render() {
return <WrapperComponent name="why" />;
}
}
NewWrapperComponent.displayName = "coderwhy"
return NewWrapperComponent;
}
-
组件的名称问题:
- 在es6中,类表达式中类名是可以省略的;
- 组件的名称都可以通过displayName来修改
-
高阶组件并不是React API 的一部分,它是基于React的组合特性而形成的设计模式;
-
高阶组件在一些React第三方库中非常常见:
- 比如: redux中的 connect;
- 比如:react-router中的withRouter;
案例
高阶函数的意义
-
我们会发现利用高阶组件可以针对某些React代码进行更加优滩的处理。
-
其实早期的React有提供组件之间的一种复用方式是mixin,目前已经不再建议使用:
- 口 Mixin 可能会相互依锁,相与耦合,不利于代码维护;
- 口 不同的Mixin中的方法可能会相互冲实;
- 口 Mixin非常多时,组件处理起来会比较麻预,甚至还要为其做相关处理,这样会給代码造成滚雪球成的复杂性;
-
当然,HOC也有自己的一些缺陷:
- 口 HOC需要在原组件上进行包惠或老嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难;
- 口 HOC可以劫持props,在不遵守约定的情況下也可能造成冲突;
-
Hooks的出現,是开创性的,它解决了很多React之前的存在的向题
- 口 比如this指向问题;
- 口 比如hoc的嵌套复杂度问题等等;
portals 和 fragment
Portals的使用
-
某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM 元素上的)
-
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案:
- 口 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment;
- 口 第二个参数 (container)是一个 DOM 元素;
ReactDoM. createPortal (child, container)
-
通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点: 然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的;
-Modal
fragment
- 在之前的开发中,我们总是在一个组件中返回内容时包裹一个div元素:
- 我们又希望可以不渲染这样一个div应该如何操作呢?
- 口 使用Fragment ,
- 口 Fragment 允许你将子列表分组,而无需向 DOM 添加额外节点;
- React还提供了Fragment的短语法:
- 口 它看起来像空标签
<></>
;
- 口 它看起来像空标签
-
- 但是,如果我们需要在Fragment中添加key,那么就不能使用短语法;
StrictMode严格模式
StrictMode
- StrictMode 是一个用来突出显示应用程序中潜在问题的工具:
- 口 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI;
- 口 它为其后代元素触发额外的检查和警告;
- 口 严格模式检查仅在开发模式下运行;它们不会影购生产物建:
- 可以为应用程序的任何部分启用严格模式:
- 口 不会对 Header 和 Footer 组件运行严格模式检查;
- 口 但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查;
严格模式检查的是什么?
- 1.识别不安全的生命周期;
- 2.使用过时的ref API;
- 3.检查意外的副作用
- 口 这个组件的constructor会被调用两次;
- 口 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用;
- 口 在生产环境中,是不会被调用两次的;
- 4.使用废弃的findDOMNode方法
- 口 在之前的React API中,可以通过findDOMNode来获取DOM,不过己经不推荐使用了,可以自行学习演练一下
- 5.检测过时的context API
- 口 早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的;
- 口 目前这种方式已经不推荐使用,大家可以自行学习了解一下它的用法
React 的过渡动画
react-transition-group库介绍React Transition Group
- 在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验。
- 当然,我们可以通过原生的CSS来实现这些过渡动画,但是React社区为我们提供了react-transition-group 用来完成过渡动画。
- React曾为开发者提供过动画插件 react-addons-css-transition-group,后由社区维护,形成了现在的 react-transition- group。
- 口 这个库可以帮助我们方便的实现组件的入场 和离场 动画,使用时需要进行额外的安装:
npm npm install react-transition-group --save
yarn yarn add react-transition-group
react-transition-group主要组件
- react-transition-group主要包含四个组件:
- Transition
- 口该组件是一个和平台无关的组件(不一定要结合CSS)
- 口在前端开发中,我们一般是结合CSS来完成样式,所以比较常用的是CSSTransition;
- CSSTransition
- 口 在前端开发中,通常使用CSSTransition来完成过渡动画效果
- SwitchTransition
- 口 两个组件显示和隐藏切换时,使用该组件
- TransitionGroup
- 口 将多个动画组件包表在其中,一般用于列表中元素的动画;
CSSTransition
- CSSTransition是基于Transition组件构建的;
- CSSTransition执行过程中,有三个状态:appear、 enter、 exit;
- 它们有三种状态,需要定义对应的CSS样式:
- 口 第一类,开始状态:对于的类是-appear、 -enter、 -exit;
- 口 第二类:执行动画:对应的类是-appear-active. -enter-active、 -exit-active;
- 口 第三类:执行结束:对应的类是-appear-done、 -enter-done、 -exit-done;