React相关知识点
1.说说你对react的理解?有哪些特性?
一、是什么?
React,用于构建用户界面的 JavaScript 库,提供了 UI 层面的解决方案
遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效
使用虚拟DOM来有效地操作DOM,遵循从高阶组件到低阶组件的单向数据流
帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面
二、特性
React特性有很多,如:
- JSX语法
- 单向数据绑定
- 虚拟DOM
- 声明式编程
- Component
三、优势
- 高效灵活
- 声明式的设计,简单使用
- 组件式开发,提高代码复用率
- 单向响应的数据流会比双向绑定的更安全,速度更快
2.说说Real diff算法是怎么运作的?
一、什么是diff算法
diff算法的设计之初就是为了节省性能,而diff算法和虚拟DOM的完美结合使得react称为了深受欢迎的前端开发框架之一。diff 是 different 的简写,这样一来,diff 算法是什么也就顾名思义了,就是找不同
二、react diff特点
同层比较,不存在跨层级
三、普通diff与reacf diff的区别?
最主要的就是算法的时间复杂度不同,普通的diff算法使用的是傻瓜式的循环递归对节点进行依次
的对比,使其算法的时间复杂度为O(n^3),而react diff通过对普通diff算法的优化,实现了时间复
杂度下降到O(n)的这样一个程度
四、运作流程
主要分为3层:tree层、component层、element层
(1)、tree层:tree层对DOM节点的跨层级移动的操作忽略不计,只对相同层级的DOM节点进行比较(即同一个父节点下的所有子节点),一旦发现节点不存在,直接删除掉该节点以及之下的所有子节点
(2)、component层:
- 遇到同一类型的组件遵循 tree diff,进行层级对比
- 遇到不同类型的组件,直接将这个不同的组件判断为脏组件,并替换该组件和之下所有的子节点
- 在同一类型的两个组件中,当知道这个组件的虚拟dom没有任何变化,就可以手动使用shouldComponentUpdate()来判断组件是否需要进行diff,进一步的提升了diff效率和性能
(3)、element层:对同一层级的元素节点进行比较,有三种情况:
- 面对全新的节点时,执行插入操作
- 面对多余的节点时,执行删除操作
- 面对换位的节点时,执行移动操作
五、 react中key值的作用
key值就是每个元素节点对应的唯一标识,要进行数据的更新,需要进行新旧虚拟dom的对比,就需要对比子元素的key值是否有匹配项,如果有则会进行数据的更新;如果没有就需要进行删除和重新创建
3.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
一、是什么
生命周期: React整个组件生命周期包括从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程
二、流程
生命周期一共分为三个阶段:
- 创建阶段
- 更新阶段
- 卸载阶段
创建阶段:
- constructor:实例过程中自动调用的方法,在方法内部通过super关键字获取来自父组件的props
在该方法中,通常的操作为初始化state状态或者在this上挂载方法 - getDerivedStateFromProps:该方法是新增的生命周期方法,是一个静态的方法,因此不能访问到组件的实例
执行时机:组件创建和更新阶段,不论是props变化还是state变化,也会调用
在每次render方法前调用,第一个参数为即将更新的props,第二个参数为上一个状态的state,可以比较props 和 state来加一些限制条件,防止无用的state更新
该方法需要返回一个新的对象作为新的state或者返回null表示state状态不需要更新 - render:类组件必须实现的方法,用于渲染DOM结构,可以访问组件state与prop属性
注意: 不要在 render 里面 setState, 否则会触发死循环导致内存崩溃 - componentDidMount:组件挂载到真实DOM节点后执行,其在render方法之后执行。
更新阶段:
- getDerivedStateFromProps:同上
- shouldComponentUpdate:用于告知组件本身基于当前的props和state是否需要重新渲染组件,默认情况返回true
执行时机:到新的props或者state时都会调用,通过返回true或者false告知组件更新与否
一般情况,不建议在该周期方法中进行深层比较,会影响效率
同时也不能调用setState,否则会导致无限循环调用更新 - render:同上
- getSnapshotBeforeUpdate:该周期函数在render后执行,执行之时DOM元素还没有被更新
该方法返回的一个Snapshot值,作为componentDidUpdate第三个参数传入
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('#enter getSnapshotBeforeUpdate');
return 'foo';
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('#enter componentDidUpdate snapshot = ', snapshot);
}
- componentDidUpdate:执行时机:组件更新结束后触发
在该方法中,可以根据前后的props和state的变化做相应的操作,如获取数据,修改DOM样式等
卸载阶段:
- componentWillUnmount:此方法用于组件卸载前,清理一些注册是监听事件,或者取消订阅的网络请求等
一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建
三、总结
4.React组件之间如何通信?
一、组件通信是什么?
React的组件灵活多样,按照不同的方式可以分成很多类型的组件
而通信指的是发送者通过某种媒体以某种格式来传递信息到收信者以达到某个目的,广义上,任何信息的交通都是通信
组件间通信即指组件通过某种方式来传递信息以达到某个目的
二、组件通信的几种方式
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
5.说说你对React中虚拟dom的理解?
虚拟 DOM 不会进行排版与重绘操作,而真实 DOM 会频繁重排与重绘
使用虚拟 DOM 的优势如下: 简单方便:如果使用手动操作真实 DOM 来完成页面,繁琐又容易出错,在大规模应用下维护起来也很困难 性能方面:使用 Virtual DOM,能够有效避免真实 DOM 数频繁更新,减少多次引起重绘与回流,提高性能 跨平台:React 借助虚拟 DOM,带来了跨平台的能力,一套代码多端运行 缺点: 在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化 首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,速度比正常稍慢
6.说说你对react hook的理解?
一、React Hook/Hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
二、三个常用的Hook
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
其他的Hook
UseContext()、UseReducer()、UseMemo()、useCallback()
三、 State Hook
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
useState()用于为函数组件引入状态。在useState()中,数组第一项为一个变量,指向状态的当前值。类似this.state,第二项是一个函数,用来更新状态,类似setState
四、 Effect Hook
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
useEffect()接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项。只要这个数组发生变化,useEffect()就会执行
五、Ref Hook
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
相当于class组件中的createRef的作用,ref.current获取绑定的对象
六、Context Hook
接收context状态树传递过来的数据
七、Reducer Hook
接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数
八、Memo和Callback Hook
接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据
共同作用:仅仅依赖数据发生变化, 才会调用,也就是起到缓存的作用。useCallback缓存函数,useMemo 缓存返回值。
7、React性能优化的手段有哪些?
- 使用纯组件;
- 避免使用内联函数:每次render渲染时,都会创建一个新的函数实例,应该在组件内部创建一个函数,讲事件绑定到函数,这样每次调用render时,就不会创建单独的函数实例
- 事件绑定方式:从性能考虑,在render方法中使用bind和箭头函数,都会生成新的方法实例,在constructer中欧给使用bind和箭头函数,性能提高
- 服务端渲染:可以使用户更快的看到显然成功的页面,服务端渲染可以起一个node服务,可以使用express。koa等,调用react的renderToString方法,将跟组件渲染成字符串,再输出到相应中
- 组件拆分:合理使用hooks
- 使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对 于相同的输入,不重复执行;
- 如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
- 路由懒加载;
- 使用 React Fragments 避免额外标记;
- 不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
- 避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
- 如果是类组件,事件函数在Constructor中绑定bind改变this指向;
- 避免使用内联样式属性;
- 优化 React 中的条件渲染;
- 不要在 render 方法中导出数据;
- 列表渲染的时候加key;
- 在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
- 类组件中使用immutable对象;
8.说说React jsx转换成真实DOM的过程?
一、JSX的本质
实际上,jsx仅仅只是 React.createElement(component, props, …children) 这个函数的语法糖。
所有的jsx最终都会被转换成React.createElement的函数调用。
createElement需要传递三个参数:
参数一:type
当前元素的类型;
如果是标签元素,那么就使用字符串表示, 例如 “div”;
如果是组件元素,那么就直接使用组件的名称;
参数二:config
所有jsx中的属性都在config中以对象的属性和值的形式存储;
比如传入className作为元素的class;
参数三:children
存放在标签中的内容,以children数组的方式进行存储;
当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方
二、React.createElement的过程
- 1.二次处理key、ref、self、source四个属性值;
- 2.遍历config,筛选可以提到props中的属性;
- 3.将children中的子元素推入childArray数组;
- 4.格式化defaultProps
- 5.将以上数据作为入参,发起ReactElement的调用,最终由ReactElement返回虚拟Dom对象
三、流程
书写JSX代码
Babel编译JSX
编译后的JSX执行React.createElement的调用
传入到ReactElement方法中生成虚拟Dom
最终返回给ReactDom.render生成真实Dom
9 .React render方法的原理,在什么时候会触发?
一、原理
类组件中render函数就是render方法
class App extends React.Component {
render() { //类组件中
return <h1> Foo </h1>;
}
}
函数组件就是整个函数组件
function App() { //函数组件中
return <h1> App </h1>;
}
在render函数中的jsx语句会编译成我们熟悉的js代码
在render过程中,React将新调用的render函数返回的树与旧版本的树进行比较·,这一步是决定如何更新dom的必要步骤,然后进行diff比较,更新dom树
二、触发
一个是类组件,一个是函数组件
类组件调用setState修改状态:
class App extends React.Component {
state = { count: 0 };
increment = () => {
const { count } = this.state;
const newCount = count < 10 ? count + 1 : count;
this.setState({ count: newCount });
};
render() {
const { count } = this.state;
console.log("App render");
return (
<div>
<h1> {count} </h1>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
函数组件通过useState修改状态:
function App() {
const [count, setCount] = useState(0);
function increment() {
const newCount = count < 10 ? count + 1 : count;
setCount(newCount);
}
console.log("App render");
return (
<div>
<h1> {count} </h1>
<button onClick={increment}>Increment</button>
</div>
);
}
函数组件通过useState这种形式更新数据,当数组的值不发生变化啦,就不会触发render
三、总结
render函数里面可以编写JSX,转化成createElement这种形式,用于生成虚拟DOM,最终转化成真实DOM
在React 中,类组件只要执行了 setState 方法,就一定会触发 render 函数执行,函数组件使用useState更改状态不一定导致重新render
组件的props 改变了,不一定触发 render 函数的执行,但是如果 props 的值来自于父组件或者祖先组件的 state
在这种情况下,父组件或者祖先组件的 state 发生了改变,就会导致子组件的重新渲染
所以,一旦执行了setState就会执行render方法,useState 会判断当前值有无发生改变确定是否执行render方法,一旦父组件发生渲染,子组件也会渲染
10、探究react的事件机制(合成事件)
一、react的事件机制
- react自身实现了一套事件机制,包括事件的注册、事件的存储、事件的合成及执行等。
- react 的所有事件并没有绑定到具体的dom节点上而是绑定在了document 上,然后由统一的事件处理程序来派发执行。
- 通过这种处理,减少了事件注册的次数,另外react还在事件合成过程中,对不同浏览器的事件进行了封装处理,抹平浏览器之间的事件差异。
二、对合成事件的理解
- (1)对原生事件的封装
react会根据原生事件类型来使用不同的合成事件对象
比如: 聚焦合成事件对象SyntheticFoucsEvent(合成事件对象:SyntheticEvent是react合成事件的基类,定义了合成事件的基础公共属性和方法。
合成事件对象就是在该基类上创建的)
- (2)不同浏览器事件兼容的处理
在对事件进行合成时,react针对不同的浏览器,也进行了事件的兼容处理
三、事件机制的流程
事件注册
在组件挂载阶段,根据组件内声明的事件类型-onclick,onchange 等,给 document 上添加事件 -addEventListener,并指定统一的事件处理程序 dispatchEvent。
事件存储
完成事件注册后,将 react dom ,事件类型,事件处理函数 fn 放入数组存储,组件挂载完成后,经过遍历把事件处理函数存储到 listenerBank(一个对象)中,缓存起来,为了在触发事件的时候可以查找到对应的事件处理方法去执行。
开始事件的存储,在 react 里所有事件的触发都是通过 dispatchEvent方法统一进行派发的,而不是在注册的时候直接注册声明的回调,来看下如何存储的 。
react 把所有的事件和事件类型以及react 组件进行关联,把这个关系保存在了一个 map里,也就是一个对象里(键值对),然后在事件触发的时候去根据当前的 组件id和 事件类型查找到对应的 事件fn
事件执行
1、进入统一的事件分发函数(dispatchEvent)
2、结合原生事件找到当前节点对应的ReactDOMComponent对象
3、开始 事件的合成
根据当前事件类型生成指定的合成对象
封装原生事件和冒泡机制
在 listenerBank事件池中查找事件回调并合成到 event(合成事件结束)
4.处理合成事件内的回调事件(事件触发完成 end)
四、合成事件、原生事件之间的冒泡执行关系
结论:
- 原生事件阻止冒泡肯定会阻止合成事件的触发。
- 合成事件的阻止冒泡不会影响原生事件。
原因:
浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段。
节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡,所以原生事件阻止冒泡会阻止合成事件的触发,而合成事件的阻止冒泡不会影响原生事件。