React相关知识

React特性及优势

用于构建用户界面的JavaScript库

特性
  • JSX 语法
  • 单向数据绑定
  • 虚拟 DOM
  • 声明式编程:一种编程范式,描述目标的性质,而非流程和命令式编程对立。
  • Component:将整个应用拆分成功能单一的部件。小部件就是组件,组件有以下特征:可组合,可复用,可维护
优势
  • 高效灵活
  • 声明式的设计,简单使用
  • 组件式开发,提高代码复用率
  • 单向响应的数据流会比双向绑定的更安全,速度更快

Real DOM和Virtual DOM的区别

Real DOM:真实DOM,文档对象模型
Virtual DOM:虚拟DOM,以JavaScript对象形式存在的对DOM的描述

区别
Real DOM Virtual DOM
排版与重绘操作 会,频繁 不会
总损耗 真实 DOM 完全增删改+排版与重绘 虚拟 DOM 增删改+真实 DOM 差异增删改+排版与重绘
优缺点
Real DOM Virtual DOM
优点 易用 简单方便、性能优、跨平台
缺点 效率低、性能差 首次渲染大量DOM,速度比正常的慢、性能要求极高的应用,不能针对性的极致优化

生命周期

从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程

过程:

创建阶段

  • constructor:初始化state状态或者在this上挂载方法
  • getDerivedStateFromProps一个静态方法,不能访问组件实例,执行时机:组件创建和更新阶段,需要返回一个新的对象作为新的state或者返回null表示state状态不需要更新
  • render类组件必须实现的方法,渲染DOM结构,可访问state和props属性,不可在此使用setState
  • componentDidMount:用于一些数据获取,事件监听等操作。

更新阶段

  • getDerivedStateFromProps:同上
  • shouldComponentUpdate:告知组件本身基于当前的props和state是否需要重新渲染组件,首次渲染或使用 forceUpdate() 时不会调用该方法,当state或props发生变化时,会在渲染执行之前被调用。作为性能优化的方式存在。(不太常用的生命周期函数)
  • render:同上
  • getSnapshotBeforeUpdate:执行之时:DOM元素还没有被更新,在于获取组件更新前的一些信息,
  • componentDidUpdate:组件更新结束后触发,根据前后的props和state的变化做相应的操作,获取获取数据,修改DOM样式等

卸载阶段

  • componentWillUnmount:用于组件卸载前,清理一些注册是监听事件,或者取消订阅的网络请求等,一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建

state和props的区别

state:数据状态
props:从外部传入组件内部的数据

区别

相同:

  • JavaScript 对象
  • 用于保存信息
  • 触发渲染更新

区别
state:

  • 在组件内被组件自己管理的
  • 组件内部可以进行修改
  • 多变的、可以修改

props:

  • 外部传递给组件的
  • 组件内部是不可修改的

super()和super(props)的区别

ES6类
super实现调用父类,用于子类继承父类的this对象
类组件

  • 类组件基于 ES6,所以在 constructor 中必须使用 super,
  • 在调用 super 过程,无论是否传入 propsReact 内部都会将 porps 赋值给组件实例 porps 属性中
  • 如果只调用了 super(),那么 this.propssuper() 和构造函数结束之间仍是 undefined

setState执行机制

组件的显示形态是由数据状态和外部参数所决定的,数据状态(state)改变数据状态通过setState("xx",()=>{})来实现,来实现改变组件内部的数据。

更新类型

  • 异步更新
 this.setState({
      message: "你好啊
    });
console.log(this.state.message); // Hello World
//解决立即获取
this.setState({
    message: "你好啊"
  }, () => {
    console.log(this.state.message); // 你好啊
  });
  • 同步更新
setTimeout(()=>{
    this.setState({
      message: "你好啊
    });
    console.log(this.state.message); // 你好啊
  })
  • setState只在合成事件和钩子函数、生命周期函数中是“异步”的,在原生事件和setTimeout 中都是同步的
  • setState很多时候并不是立即执行,所以在setState之后立即调用state可能不会得到理想的state的值。componentDidUpdate 或者 setState 的回调函数(setState(updater, callback))这两种方式都可以保证在应用更新后触发
  • setState 通过一个队列机制实现 state 更新。当执行 setState 时,会将需要更新的 state 合并后放入状态队列,而不会立刻更新 this.state,队列机制可以高效地批量更新 state。

setState注意事件
state的更新可能是异步的,所以不要期待用当前state的值来更新state
解决方法
setState接受一个函数而不是一个参数

React事件机制

React基于浏览器事件机制,实现自己的一套事件机制,包含事件注册、事件的合成、事件冒泡、事件派发等。通称为合成事件

事件执行顺序
  • React 所有事件都挂载在 document 对象上
  • 当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件
  • 所以会先执行原生事件,然后处理 React 事件
  • 最后真正执行 document 上挂载的事件

阻止事件冒泡不同时间段的方法

  • 阻止合成事件间的冒泡,用e.stopPropagation()
  • 阻止合成事件与最外层 document 上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation()
  • 阻止合成事件与除最外层document上的原生事件上的冒泡,通过判断e.target来避免
事件机制
  • React上注册的事件都绑定在document上的DOM节点上。不是React组件上对应的DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
  • React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation()无效的原因。
  • React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback
  • React 有一套自己的合成事件 SyntheticEvent

React绑定事件的方法

采用小驼峰命名方式,把事件放在{}中

方式:
  • render方法中使用bind绑定
  • render方法使用箭头函数
  • constructor中bind
  • 定义阶段使用箭头函数绑定
区别

编程方面:方法一和二写法简单,三过于冗杂
性能方面:方法一和二在每次render时都会重新生成新的实例,性能欠佳,方法三和四只会生成一个方法实例,
纵上所述方法四是最优

构建组件的方法

构建方法
  • 函数式创建(推荐hooks)
  • 通过 React.createClass 方法创建(不建议使用)
  • 继承 React.Component 创建

组件间通讯

通讯方式
  • 父组件向子组件传递
    通过props来实现
  • 子组件向父组件传递
    父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值
  • 兄弟组件之间的通信
    父组件作为中间层来实现数据的互通
  • 父组件向后代组件传递
    通过context提供了组件之间通讯的一种方式来实现
  • 非关系组件传递
    通过redux或Mobx

React中key的作用

在diff算法中,key属性的作用是用于判断元素是新创建的还是被移动的元素,从而减少不必要的元素渲染
注意事项:

  • key 应该是唯一的
  • key不要使用随机值(随机数在下一次 render 时,会重新生成一个数字)
  • 使用 index 作为 key值,对性能没有优化

React中refs的使用

弹性文件系统
允许访问 DOM节点或在 render方法中创建的 React元素本质为ReactDOM.render()返回的组件实例,如果是渲染组件则返回的是组件实例,如果渲染dom则返回的是具体的dom节点

使用方法
  • 传入字符串,使用时通过 this.refs.传入的字符串的格式获取对应的元素
  • 传入对象,对象是通过 React.createRef() 方式创建出来,使用时获取到创建的对象中存在 current 属性就是对应的元素
  • 传入函数,该函数会在 DOM 被挂载时进行回调,这个函数会传入一个 元素对象,可以自己保存,使用时,直接拿到之前保存的元素对象即可
  • 传入hook,hook是通过 useRef() 方式创建,使用时通过生成hook对象的 current 属性就是对应的元素
使用场景
  • 对Dom元素的焦点控制、内容选择、控制
  • 对Dom元素的内容设置及媒体播放
  • 对Dom元素的操作和对组件实例的操作
  • 集成第三方 DOM 库

类组件和函数组件的异同

类组件:通过ES6中类的编写形式的React组件,必须实现render方法
函数组件:通过函数形式编写的React组件,第一个参数props,用来接受父组件传来的参数

区别:
类组件 函数组件
编写方式 采用ES6中的类 函数
状态管理 无,使用hooks可以实现
生命周期 无,采用hooks中的useEffect可以替代类组件中的生命周期
调用方式 将组件进行实例化,在执行render方法 执行函数
获取渲染的值 更新 不更新

受控组件和非受控组件

受控组件:受我们控制的组件,组件的状态全程响应外部数据,需要初始状态和一个状态更新事件函数
非受控组件:在初始化的时候接受外部数据,然后自己在内部存储其自身状态,通过ref查询获取当前的值

高阶组件

接受一个或多个组件作为参数并且返回一个组件,本质是一个函数,功能是封装并分离组件的通用逻辑,让通用逻辑在组件间更好地被复用
遵循原则:

  • props 保持一致
  • 你不能在函数式(无状态)组件上使用 ref 属性,因为它没有实例
  • 不要以任何方式改变原始组件 WrappedComponent
  • 透传不相关 props 属性给被包裹的组件 WrappedComponent
  • 不要再 render() 方法中使用高阶组件
  • 使用 compose 组合高阶组件
  • 包装显示名字以便于调试

使用场景
用于与核心业务无关但又在多个模块使用的功能

Hooks的理解

实现在不编写class的情况下,可以使用state和React其他特性。为了解决:

  • 难以重用和共享组件中的与状态相关的逻辑
  • 逻辑复杂的组件难以开发与维护,当我们的组件需要处理多个互不相关的 local state 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面
  • 类组件中的this增加学习成本,类组件在基于现有工具的优化上存在些许问题
  • 由于业务变动,函数组件不得不改为类组件等等

常用API
useState
useEffect
useReducer
useCallback
useMemo
useRef

引入css的方式

方式:
  • 在组件内直接使用
  • 组件中引入 .css 文件
  • 组件中引入 .module.css 文件
  • CSS in JS
特点
  • 在组件内直接使用css该方式编写方便容易能够根据状态修改样式属性,但是大量的演示编写容易导致代码混乱
  • 组件中引入 .css 文件符合我们日常的编写习惯,但是作用域是全局的,样式之间会层叠
  • 引入.module.css 文件能够解决局部作用域问题,但是不方便动态修改样式,需要使用内联的方式进行样式的编写
  • 通过css in js 这种方法,可以满足大部分场景的应用,可以类似于预处理器一样样式嵌套、定义、修改状态等

Redux

一种状态管理工具, 遵循 单一数据源,state 是只读的,使用纯函数来执行修改原则。

工作原理

把数据存放在store公共存储空间,一个组件改变store中的数据内容,其他组件感知到变化,并且获取到改变的内容,从而间接的实现数据的传递功能。

使用方法
  • createStore可以帮助创建 store
  • store.dispatch 帮助派发 action , action 会传递给 store
  • store.getState 这个方法可以帮助获取 store 里边所有的数据内容
  • store.subscrible 方法订阅 store 的改变,只要 store 发生改变, store.subscrible 这个函数接收的这个回调函数就会被执行

React-Router中

无刷新的情况下,显示不同的内容的页面。前端路由实现单页面应用

react-router:

react-router:路由核心功能
react-router-dom:基于 react-router,加入了在浏览器运行环境下的一些功能
react-router-native:基于 react-router,加入了 react-native 运行环境下的一些功能
react-router-config:用于配置静态路由的工具库

React-router-dom API

  • BrowserRouter、HashRouter
    对路径改变的监听,并且会将相应的路径传递给子组件,BrowserRouter是history模式、HashRouter模式,作为最顶层组件包裹其他组件
  • Route
    用于路径的匹配,参数如下:
  • path 属性:用于设置匹配到的路径
  • component 属性:设置匹配到路径后,渲染的组件
  • render 属性:设置匹配到路径后,渲染的内容
  • exact 属性:开启精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件
  • Link、NavLink
    路径的跳转,后者是前者的加强
  • switch
    当匹配到第一个组件的时候,后面的组件就不应该继续匹配
  • redirect
    路由重定向
    Hooks相关API
  • useHistory
    组件内部直接访问history
  • useParams
    返回URL参数键/值对的对象,返回当前<Route>mach,params
  • useLocation
    返回当前 URL的 location对象
传参方式
  • 动态路由的方式
  • search传递参数
  • to传入对象

Router的模式

  • hash 模式:在url后面加上#,如http://127.0.0.1:5500/home/#/page1
  • history 模式:允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录
使用方式

对应组件HashRouter
对用组件BrowserRouter

实现原理

immutable的理解

不可改变的,一旦创建,就不能再被更改的数据,对immutable对象的任何修改添加删除操作都会返回一个新的immutable对象。实现原理:
持续化数据结构

  • 用一种数据结构来保存数据
  • 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费

React中render方法原理

render方法的形式:

  • 在类组件中render(){}
  • 在函数组件中return
    在render中首先编写成jsx然后通过babel编译成js格式
触发时机
  • 类组件调用setState 修改状态
    只用执行setState就会执行render
  • 函数组件通过useState hook修改状态
    useState不一定执行render方法
  • 类组件重新渲染
  • 函数组件重新渲染
    组件props改变不一定执行render,props来自父组件或祖先组价的state发生变化,子组件一定会render

提升渲染效率

方法

  • shouldComponentUpdate
    比对 state和 props,确定是否要重新渲染,默认情况下返回true(重新渲染)
  • PureComponent
    跟shouldComponentUpdate原理基本一致,通过对 props 和 state的浅比较结果来实现
  • React.memo
    用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent 十分类似,只能用于函数组件。

React中diff原理

是什么
更高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处
原理

  • tree层级
    DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较,只有删除、创建操作,没有移动操作
  • conponent 层级
    如果是同一个类的组件,则会继续往下diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的
  • element 层级
    对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识
    提供了 3 种节点操作,分别为 INSERT_MARKUP(插入)、MOVE_EXISTING (移动)和 REMOVE_NODE (删除)

参数说明

  • index: 新集合的遍历下标。
  • oldIndex:当前节点在老集合中的下标
  • maxIndex:在新集合访问过的节点中,其在老集合的最大下标

操作过程规则

  • 当oldIndex>maxIndex时,将oldIndex的值赋值给maxIndex
  • 当oldIndex=maxIndex时,不操作
  • 当oldIndex<maxIndex时,将当前节点移动到index的位置

Fiber架构理解

解决JavaScript线程和页面渲染线程互斥,导致页面响应度变差,用户可能会感觉到卡顿。
是什么
对核心算法进行一次重新实现
** 做了什么操作**

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
  • dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行

架构层面:核心算法(即调和过程)的重写
代码层面:所定义的一种数据结构,虚拟DOM
fiber是一个javascript对象,包含了元素的信息、该元素的更新操作队列、类型。
如何解决
fiber把渲染过程拆分成多个子任务,每次只做一小部分,看剩余时间,有执行下一个,没有挂起,时间控制权交给主线程,主线不忙时继续执行挂起的任务,可中断和恢复,恢复后可复用之前的中间状态,并赋予不同的优先级。即(时间切片和任务调度)。

JSX转真实DOM的过程

把组件编写的JSX,映射到屏幕上,以及组件中的状态发生变化变化更新到屏幕上,babel编译中首先判断首字母的大小写,如果是小写,认定原始DOM,编译成字符串,当首字母是大写,认定为自定义组件,编译成对象,最后通过RenderDOM.render(...)挂载.
过程
节点分类:

  • 原生标签节点
  • 文本节点
  • 函数组件
  • 类组件

渲染过程:

  • 使用React.createElement或JSX编写React组件,实际上所有的 JSX 代码最后都会转换成React.createElement(...) ,Babel帮助我们完成了这个转换的过程。
  • createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
  • ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM

性能优化

  • 避免不必要的render
    解决方案:shouldComponentUpdate、PureComponent、React.memo
  • 避免使用内联函数
  • 使用 React Fragments 避免额外标记
  • 使用 Immutable
  • 懒加载组件
  • 事件绑定方式
  • 服务端渲染
  • 组件拆分、合理使用hooks

总结

  • 代码层面
  • 工程层面
  • 框架机制层面

React中如何错误捕获

错误边界概念
一种组件,可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树,在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误
形成错误边界组件的条件

  • static getDerivedStateFromError()
    渲染备用 UI
  • componentDidCatch()
    打印错误信息
    无法捕获
  • 事件处理
  • 异步代码
  • 服务端渲染
  • 自身抛出来的错误
    解决方法:try...catch...或监听onerror事件

React服务端渲染的实现

SSR(Server-Side Rendering):服务端渲染,由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程,解决了:SEO,首屏白屏问题。
实现
1.手动搭建SRR框架
2.使用成熟的SRR框架Next.js

原理

  • node server 接收客户端请求,得到当前的请求url 路径,然后在已有的路由表内查找到对应的组件,拿到需要请求的数据,将数据作为 propscontext或者store 形式传入组件
  • 然后基于 react 内置的服务端渲染方法 renderToString()把组件渲染为 html字符串在把最终的 html进行输出前需要将数据注入到浏览器端
  • 浏览器开始进行渲染和节点对比,然后执行完成组件内事件绑定和一些交互,浏览器重用了服务端输出的 html 节点,整个流程结束

流程:
服务端运行React代码生成HTML
把生成的HTML发送浏览器
浏览器接受显示内容
浏览器加载js文件
js代码执行并接管页面操作

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容