React学习笔记

React学习笔记

虚拟DOM

1、虚拟DOM的两种创建方式

1、使用JSX创建虚拟DOM
JSX可以更加方便的创建虚拟DOM,语法糖
在这里插入图片描述
2、使用JS创建虚拟DOM
在这里插入图片描述

2、JSX

JSX 是react定义的一种类似于XML的JS扩展语法;本质是React.createElement(component, prop)的语法糖,JSX可以更加方便的创建虚拟DOM

类组件、函数组件

类组件

Extends React.Component 定义的组件;React.Component是以ES6的形式来创建react的组件的,是React目前极为推荐的创建有状态组件的方式,最终会取代React.createClass形式

<script type="text/babel">
// 创建类式组件
class Weather extends React.Component {
  state = {isHot: false}
  render() {
    return  <h2 onClick={this.click}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h2>
  }
  click = () => {
    const isHot = this.state.isHot;
   this.setState({
     isHot: !isHot
   })
  }
}

// 渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('example'))

</script>

函数式定义的无 状态组件

纯函数组件的特点:
1、组件不会被实例化,整体渲染性能得到提升
2、组件不能访问this对象
3、组件无法访问生命周期的方法
4、无状态组件只能访问输入的props,无副作用

function DemoComponent(props) { // 函数名要大写
  return <div>Hello {props.name}</div>
}
ReactDOM.render(<DemoComponent name="JS每日一题" />, mountNode)

使用场景
以类形式创建的组件不用多说,该怎么用还怎么用, 这里说一纯函数组件, 纯函数组件被鼓励在大型项目中尽可能以简单的写法来分割原本庞大的组件,未来React也会将这种面向无状态组件在譬如无意义的检查和内存分配领域进行一系列优化,所以只要有可能,尽量使用无状态组件

组件传值

props

类式组件接收props

直接使用this.props.xxx使用props中的属性在这里插入图片描述

函数式组件接收props

函数组件中的props是一个对象直接作为函数的参数传入,在组件内容中可以直接props.xxx使用props对象中的属性

function Demo(props){
   return (
    <div>{props.name}</div>
   )
}
const element = <Demo name='ty'/>;
ReactDOM.render(element, document.getElementById('app')

组件通信

组件还可以通过消息订阅者发布者 模式通信 pubsub-js
几种通信方式:
1、props: children props、render props (父子之间)
2、消息订阅发布:pubs-sub、event等等 (兄弟之间)
3、集中式管理:redux(兄弟之间)
4、conText:生产者-消费者模式 (祖孙之间)

React 生命周期(旧)

constructor; 构造器
componentWillMount;组件挂载之前
componentDidMount; 组件挂载完毕(常用)一般做一些初始化的事情,例如,开启定时器、发送网络请求、订阅消息
componentWillReceiveProps; 父组件render时,子组件将要接收props
shouldComponentUpdate; setState()更新 是否更新状态;
conmponenWillUpdate; 组件将要更新之前
render;渲染
componentDidUpdate;组件更新完毕
componentWillUnmount; 组件销毁之前(常用)收尾,例如 关闭定时器、取消订阅消息

1、初始化阶段:由React.render()触发 — 初次渲染
1. constructor
2. componentWillMount
3. render
4. componentDidMount
2、更新阶段:由组件内部setState()或父组件render触发
1. shouldComponentUpdate
2. componentWillUpdate
3. render
4. componentDidUpdate
3、卸载组件:由ReactDom.unmountComponentAtNode()触发
1. componentWillUnmount

React 生命周期(新)

componentWillMount => UNSAFE_componentWillMount
componentWillReceiveProps => UNSAFE_componentWillReceiveProps
componentWillUnmount => UNSAFE_componentWillUnmount

React 新版本的生命周期相对对旧版本 废弃了componentWillMount,componentWillReceiveProps,componentWillUnmount三个钩子,添加了getDerivedStateFromProps和getSnapshotBeforeUpdate两个;
getDerivedStateFromProps(从props获取派生的state):若state的值都取决于props,可以使用
getSnapshotBeforeUpdate(prevProps, prevState,snapshotValue)(在更新之前获取快照)。组件在发生改变之前从DOM中捕获一些信息(例如,滚动位置),返回值将作为参数传递给componentDidUpdate()

React 虚拟DOM Diff算法

react/vue 中的key有什么作用

为什么遍历列表时,key最好不要用index
答:
1、虚拟DOM中key的作用: Key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用,
当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】和【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
[1]. 若虚拟DOM中内容没变,直接使用之前的真实DOM
[2]. 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到页面
2、用index作为key可能会引发的问题:
【1】若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新
【2】如果结构中还包含输入类DOM:会产生错误DOM更新
【3】如果不存在对数据逆序添加、删除等破坏顺序操作,仅用于渲染列表展示,使用index作为key是没有问题的

React脚手架

react代理

1、package.json proxy
2、setUpProxy.js

react路由

一个路由就是一个映射关系;

后端路由:key:请求路径, value: function(){}:处理请求
前端路由:key:请求路径,value:Component

react-router-dom

prefix=D:\Program Files\nodejs\node_global
cache=D:\Program Files\nodejs\node_cache
registry=http://registry.npmjs.org/

NAVLink

NavLink可以实现路由链接的高亮,通过activeClassName指定样式名

向路由组件传递参数

1、params参数

路由链接:<Link to='/test/tom/18'>Test </Link>
注册路由: <Route path='/test/:name/:age' component={Test}/>
接收参数:this.props.match.params

2、search参数

路由链接:<Link to='/test?name=tom&age=18'>Test </Link>
注册路由: <Route path='/test' component={Test}/>
接收参数:this.props.location.search(是urlencoded编码字符串,需要借助querystring解析)

3、state参数

路由链接:<Link to={{pathname:'/test', state: {name: 'tom', age: 18}}}>Test </Link>
注册路由: <Route path='/test' component={Test}/>
接收参数:this.props.location.state = ({name: 'tom', age: 18})

withRouter

withRouter可以将一般组件加上路由组件的api,history\location等
withRouter(一般组件)

BrowserRouter 、 HashRouter

区别:
1、底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
HashRouter使用的是URL的哈希值
2、URL的表现形式不一样
BrowserRouter的路径中没有#
HashRouter的路径中包含#
3、刷新后对路由state参数的影响
BrowserRouter 没有任何影响,state会保存在history对象中
HashRouter属性后会导致路由state参数的丢失

redux

全局状态管理, 什么情况下需要使用redux
1、某个组件的状态需要让其他组件可以随时拿到。
2、一个组件需要改变另一个组件的状态。

在这里插入图片描述
1、Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
Redux 提供createStore这个函数,用来生成 Store。
在这里插入图片描述
2、Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
当前时刻的 State,可以通过store.getState()拿到。
3、State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,
可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。
4、store.dispatch()
store.dispatch()是 View 发出 Action 的唯一方法。store.dispatch接受一个 Action 对象作为参数,将它发送出去。
5、Reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
6、纯函数
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。
纯函数是函数式编程的概念,必须遵守以下一些约束。
不得改写参数
不能调用系统 I/O 的API
不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
7、store.subscribe()
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
action分为两种,值为Object是同步action,值为函数是异步action

react-redux

1、需要定义一个容器组件,容器组件和UI组件整合成一个文件;
2、给包裹一个 就可以给所有要使用的组件传递store
3、使用了react-redux后不用自己监测redux中状态的改变了,容器组件可以自动完成这个工作
4、一个组件要和redux打交道经过哪些步骤?
1. 定义好UI组件
2. 引入connect生成一个容器组件,并暴露,写法如下:
connecct(
state => {
key: value // 映射状态
},
{ key: xxxAction} // 映射操作状态的方法
)
3. 在UI组件中通过this.props.xxxx读取和操作状态

React 性能优化

setState更新状态的两种写法

1、setState(stateChange,[callback]) — 对象式的setState,stateChange为状态改变对象
2、setSate(updater,[callback]) —函数式的setState,updater为返回stateChange对象的函数,可以接受到state、props
3、使用:如果新的状态依赖于原状态可以使用函数式的setState

lazyLoad

路由组件的懒加载

// 通过React的lazy函数配合import()函数动态加载路由组件
const Login = lazy(() => import('/pages/Login)

//通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>Loading....</h1>}
   <Switch>
	   <Route path='/xxx' component={Login}
	   <Redirect to='/login'>
   </Switch>
</Suspense>

Hooks

React Hooks是React 16.8.0 版本增加的新特性/新语法,可以让你在函数组件中使用state以及其他的React特性

State Hook

1、State Hook可以让函数组件也可以有state状态,进行读写操作
2、语法:const [xxx, setXxx]=React.useState(initValue)
3、useState(initValue),initValue是第一次初始化指定的值在内部做缓存,返回一个包含两个元素的数组,第一个为内部当前状态值,第二个为更新状态的函数
4、setXxx() 有两种写法:1. setXxx(newValue):参数为非函数值,直接指定新的状态值;2. setXxx(value => newValue):参数为函数,接收原本的状态值,返回新的状态值

Effect Hook

1、Effect Hook可以让你在函数组件中执行副作用操作(模拟类组件中的生命周期钩子)
2、语法:

useEffect( () => {
// 在此可以执行任何带副作用的操作 (模拟componentDidMount、componentDidUpdate)
xxx
return ()=> {  // 在组件卸载前执行
// 在此做一些收尾工作
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
Ref Hook

1、Ref Hook 可以在函数组件中存储、查找组件内的标签或者其他任意数据
2、语法:const refContainer= userRef();功能与React.createRef()一样

Hooks的出现解决了类组件的什么问题

1、类组件和函数组件对比(函数组件代码量少,简单)
2、类组件可以访问生命周期方法,函数组件做不到
3、类组件可以获取实例化后的this,
4、有状态组件、无状态组件
5、类组件的问题
- 组件可以复用,但是组件内的状态呢,肯定没办法复用;业务量上来之后,Hooks可以让我们无需修改组件结构,复用组件
- 组件不好拆分
6、class本身是语法糖,压缩后代码量大
7、更贴合函数式编程的感觉
8、因为减少了很多模板代码,特别是小组件写起来更加省事,人们更愿意去拆分组件。而组件粒度越细,被复用的可能性越大。所以,hooks也在不知不觉中改变人们的开发习惯,提高项目的组件复用率。
9、hooks很好用很强大,但它不擅长异步。但在有太多异步逻辑的代码时,class比它更适合、更稳、更好维护。

Fragment

使用Fagment可以不必须 有一个真实的DOM根标签了

<Fragment> </Fragment>
或者
<></>

Context

Context可以用于祖组件与后代组件间通信
使用:

1、创建Context容器对象:
      const XxxContext = React.createContext()
2、渲染子组件时,外面包裹XxxContext.Provider,通过value属性给后代组件传递数据:
      <xxxContext.Provider value={数据}>
         子组件
      </xxxContext.Provider>

3、后代组件读取数据:

// 第一种方式,仅适用于类组件
static contextType = xxxContext // 声明接收context
this.context // 读取context中的value数据

// 第二种方式,都可用
<xxxContext.Consumer>
{
   value => ( // value就是context中的value数据
      要显示的内容
   )
}
</xxxContexxt.Consumer>

组件优化

Component存在的两个问题:
1、只要执行setState,即使不改变状态数据组件也会重新render
2、只要当前组件重新render,就会自动重新render子组件
原因:Component中的shouldComponentUpdate()总是返回true
解决办法:使用PureComponent
PureComponent重写了shouldComponentUpdate方法比较新旧状态(浅对比)再判断是否重新render

Render Props (插槽)

向组件内部动态传入带内容的结构(标签)

<A render={(data) => <C data = {data}></C></A>
A组件:{this.props.render()}
C组件:读取A钻进传入的数据显示{this.props.data}

错误边界

错误边界,用于捕获后代组件错误,渲染出备用页面
只能捕获后代组件生命周期产生的错误
使用方式:

getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后代组件报错,就会触发
static getDerivedStateFromError(error) {
   console.log(error);
   // 在render之前触发
   // 返回新的state
   return {
      hasError: true
   }
}
componentDidCatch(error, info){
  // 统计页面错误,发送给后台
  console.log(error, info)
}

React MS Qusetions

对React的理解

React是一个用于构建用户界面的Js库,可以通过组件化的方式构建快速响应的大型Web应用。
三个特性:
1、声明式
声明式渲染:我们只需要告诉程序我们想要什么效果,其它的交给程序来做
命令式:命令程序去做什么,程序跟着你的命令一步一步执行
2、组件化
把页面拆分成一个个组件,方便视图的拆分和复用,高内聚,低耦合
3、一次学习、随编写
可以用来开发web端、移动端(虚拟DOM可以实现跨平台)
缺点: 没有官方的系统解决方案,选型成本高,不像vue有全家桶;过于灵活

React 的 diff 算法工作过程

1、diff算法的作用:计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面
2、传统diff算法: 通过循环递归对节点依次进行比较,算法复杂度达到O(n^3),n是树的节点数
3、React的diff算法, diff策略

策略1:tree diff

(1) React通过updateDepth对Virtual DOM树进行层级控制
(2) 对树分层比较,两棵树只对同一层次节点进行比较。如果该节点不存在,则该节点及其子节点会被完全删除,不会再进一步比较
(3) 只需遍历一次,就能完成整棵DOM树的比较

策略2:component diff

React对不同的组件间的比较有三种策略:
(1) 同一类型的两个组件,按层级比较继续比较VirtualDOM树即可
(2) 同一类型的两个组件,组件A变化为组件B时,可能VirtualDOM没有任何变化,所以,用户可以通过shouldComponentUpdate()来判断是否需要重新计算
(3) 不同类型的组件,将原组件判断为dirty component,从而替换整个组件的所有节点
组件类型判断:https://segmentfault.com/a/1190000039645613

策略3:element diff

对于同一层级的一组子节点,通过唯一id区分。
当节点处于同一层级时,diff提供三种节点操作:删除、插入、移动

setState()是同步还是异步的

可以是同步的,也可以是异步的;

  • 在setTimeout和原始事件中,可以立即拿到更新结果。也就是同步
  • 在合成事件个生命周期中,不能立即拿到更新结果,也就是所谓的“异步”
  • 在合成事件和生命周期中,如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行

React定义了一个内部变量executionContext(默认为NoContext),在进行合成事件和生命周期处理的时候,会首先给该变量赋值为DiscreteEventContext(合成事件),来标记其现在所处的执行环境,而在setTimeout以及原生事件中,是脱离了这些执行环境的,executionContext就是默认值NoContext。 在scheduleWork(事件调度)处理逻辑的时候,如果执行环境不为NoContext,则仅仅是将更新放在一个队列里面,不进行实际的应用;

结论:是否是同步更新的,取决于其执行环境,因为setTimeout和原生事件脱离了原本的执行环境,所以state的更新为同步更新
结论:对生命周期或者合成事件包裹了一层try { // 执行,更新放队列 } finally { // 更新state },最后在finally中进行的state更新。所谓的异步并不是真正的异步,而是先将更新放在了队列里面,当代码执行完后,再(在 finally 中)一次性处理这些更新

React合成事件
// 下面代码并不是将click事件直接绑定在dom上,而是采用事件冒泡的形式冒泡到document上面,然后React将事件封装给正式的函数处理运行和处理
<div className="testDom" onClick={this.testDomClick()}><div>

为什么要使用合成事件?
如果DOM上绑定了过多的事件处理函数,整个页面响应以及内存占用可能都会受到影响,React为了避免这类DOM事件滥用。同时屏蔽底层不同浏览器质检的事件系统的差异,实现了一个中层——SyntheticEvent(负责所有事件合成)

React、Vue /Angular 的区别?

react 不是框架,应该说是一个js库,更适合复杂而灵活的项目,对js要求较高,灵活性胜过功能性
vue 全家桶更加容易上手,代码整洁
angular 是一种框架基于TypeScript,Angular会涉及到依赖注入(Dependency Injection),即一个对象为另一个对象提供各种依赖。这将使得代码能够更加简洁,更加易懂。另一方面,Angular是基于MVC架构的,而MVC模式会将项目拆分成模型、视图和控制器三种不同的组件。如果您喜欢面向对象的编程,那么Angular绝对是您的最佳选择。

React 中各种组件复用的优劣势(mixin、render props、hoc、hook)

HOC 高阶组件

是react中的高级技术,用来重用组件逻辑,一个入参是组件,返回也是组件的函数

React 路由有3种渲染方式——render,children,component,到底用哪一个?

children:func
使用场景:有时候,不管location是否匹配,你都需要渲染⼀些内容,这时候你可以⽤children。除了不管location是否匹配都会被渲染之外,其它⼯作⽅法与render完全⼀样。
render:func
但是当你⽤render的时候,你调⽤的只是个函数。但是它和component⼀样,能访问到所有的[route props]。
component: component
场景:只在当location匹配的时候渲染。

React 异步render

React 性能瓶颈:当JS任务执行时间过长时,浏览器的刷新频率为60HZ,大概16.6ms渲染一次,而JS线程和渲染线程是互斥的,所以如果JS线程执行任务时间超过16.6ms的话,就会导致掉帧,导致卡顿,解决方案就是React利用空闲时间进行更新,不影响渲染线程的渲染;把一个耗时任务切分成一个个小任务,分布在每一帧里的方式就叫时间切片

//将一个大的、耗时的JS任务拆分成若干个小的js任务(5ms左右),异步可中断
ReactDOM.unstable_createRoot(document.getElementById('root').render(</App>)

React Fiber

  • 我们可以通过某些调度策略合理分配CPU资源,从而提高用户的响应速度
  • 通过Fiber架构,让自己的调和过程变成可被中断,适时地让出CPU执行权,可以让浏览器及时的响应用户的交互
同步更新过程的局限

在以前的React中,更新过程是同步的,这可能会导致性能问题。
当React决定要加载或者更新组件树时,会做很多事,比如调用各个组件的生命周期函数,计算和比对VirtualDOM,最后更新DOM树,这整个过程是同步进行的,也就是说只要一个加载或者更新过程开始,那React就会一鼓作气运行到底,中途绝不停歇。
假如更新一个组件需要一毫秒,如果有两百个组件要更新,那就需要200毫秒,在这200毫秒内,用户往一个input框中输入点什么,敲击键盘也不会获得响应,因为渲染输入按键结果也是浏览器的主线程的工作,但是浏览器主线程被React占着呢,最后的结果就是用户敲了按键看不到反应,等React更新过程结束之后,那些按键一下子出现在input元素里了。这就是所谓的界面卡顿。
因为JS单线程的特点,每个同步任务不能耗时太长,不然会让浏览器不会对其它输入作出响应,React的更新过程就是犯了这个禁忌,而ReactFiber就是要改变这种现状。

React Fiber的方式

破解JS中同步操作时间过长的方法就是——分片。
React Fiber把更新过程碎片化,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续更新,如果有紧急任务,那就去做紧急任务,维护每一个分片的数据结构,就是Fiber

Fiber是一种数据结构
  • React目前的做法是使用链表,每个VirtualDOM节点内部表示为一个Fiber
  • 从顶点开始遍历
  • 如果有第一个儿子,先遍历第一个儿子
  • 如果没有第一个儿子,标志着此节点遍历完成
  • 如果有弟弟、遍历弟弟
  • 如果没有下一个弟弟,返回父节点表示完成父节点遍历,如果有叔叔遍历叔叔
  • 没有父节点遍历结束
    在这里插入图片描述
    React Fiber对现有代码的影响:
    因为一次更新过程会分成多个分片完成,所以完全有可能一个更新任务还没有完成,就被另一个更高优先级的更新过程打断,这时候,优先级高的更新任务会优先处理完成,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来。
    因为一个更新过程可能被打断,所以React Fiber一个更新过程被分为两个阶段(Phase):第一个阶段Reconciliation Phase(调和阶段)和第二阶段Commit Phase
    在第一阶段Reconciliation Phase,ReactFiber 会找出需要更新哪些DOM,这个阶段是可以被打断的;但是到了第二阶段Commit Phase,那就会一鼓作气把DOM更新完,绝不会被打断
    以render函数为界,第一阶段可能会调用下面这些生命周期函数:
    • componentWillMount
    • componentWIllReceiveProps
    • shouldComponentUpdate
    • componentWIllUpdate
      下面这些生命周期函数则会在第二阶段调用
    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount
      所以在ReactFiber中,第一阶段中的生命周期函数在一次加载和更新过程中可能会被多次调用;
      所以React 16.8以后已经不推荐使用componentWillMount、 componentWIllReceivePros、componentWIllUpdate

JSX怎么映射虚拟DOM

JSX就是React.createElement()的语法糖,createElement(type,config,children)对传入的参数进行判断、遍历等操作拆解;ReactElement()调用生产虚拟DOM对象

虚拟DOM映射到真实DOM

解析ReactDOM.render()的执行过程:
【1】 判断传入的虚拟DOM的类型

  • 普通文本类型,string或number,直接创建一个真实文本节点
  • 普通的HTML标签,比如
    ,创建一个真实的DOM节点,将参数的props属性中的一些普通属性挂载到这个真实的DOM节点上,接着处理children
  • 函数组件
  • 类组件
    【2】将创建的真实节点插入到父节点中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值