关于虚拟DOM:
1.本质是Object类型的对象(一般对象)
2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
jsx语法规则:
1.定义虚拟DOM时,不要写引号。
2.标签中混入JS表达式时要用{}。
3.样式的类名指定不要用class,要用className。
4.内联样式,要用style=key:value的形式去写。
5.只有一个根标签
6.标签必须闭合
7.标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
2.1.2 组件的定义
函数式组件(适用于简单组件)
类式组件(适用于复杂组件)
注意:
组件名必须首字母大写
虚拟DOM元素只能有一个根元素
虚拟DOM元素必须有结束标签
state
state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
组件被称为”状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
props
每个组件对象都会有props(properties的简写)属性
组件标签的所有属性都保存在props中
2.4 组件的三大核心属性3:refs与事件处理
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
—
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)或者f(a, b)(c)或者f(a)(b, c)
应用:
参数复用:即如果函数有重复使用到的参数,可以利用柯里化,将复用的参数存储起来,不需要每次都传相同的参数
延迟执行:传入参数个数没有满足原函数入参个数,都不会立即返回结果,而是返回一个函数。(bind方法就是柯里化的一个示例)
函数式编程中,作为compose, functor, monad 等实现的基础
重要的勾子
render:初始化渲染或更新渲染调用
render() 函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。
如需与浏览器进行交互,请在 componentDidMount() 或其他生命周期方法中执行你的操作。保持 render() 为纯函数,可以使组件更容易使用、维护。
componentDidMount:开启监听, 发送ajax请求
componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。
componentDidMount 是一个会阻塞渲染的生命周期,我们在这里面最好不要去执行一些非常耗时的逻辑,这样会让我们的首屏出现的更慢。
componentWillUnmount:做一些收尾工作, 如: 清理定时器
虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key:
根据数据创建新的真实DOM,随后渲染到到页面
用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。 2. 如果结构中还包含输入类的DOM: 会产生错误DOM更新 ==> 界面有问题。 3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作, 仅用于渲染列表用于展示,使用index作为key是没有问题的。
开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。 2.如果确定只是简单的展示数据,用index也是可以的。处链接及本声明。
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
Props/state改变 –> render函数重新执行 –> 产生新的DOM树–>新旧DOM树进行diff –>计算出差异进行更新–>更新到真实的DOM上
–Router中包含了对路径的改变的监听,并且将会相应的路径传递给子组件
–BrowserRouter使用history模式
–HashRouter使用hash模式
(2) Link和NavLink
–通常路径的跳转是使用Link组件,最终会被渲染成a 元素
–NavLink是在Link基础之上增加了一些样式属性
–to属性:LInk最重要的属性,用于设置跳转到的路径
(3) Route:
–Route用于路径的匹配
–path属性:用于设置匹配到的路径
–component属性:设置匹配到路径后,渲染组件
–exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
useEffect使用技巧
1、不传递
useEffect不传递第二个参数会导致每次渲染都会运行useEffect。然后,当它运行时,它获取数据并更新状态。然后,一旦状态更新,组件将重新呈现,这将再次触发useEffect,这就是问题所在。
2.传递空数组(仅在挂载和卸载的时候执行)
useEffect(()=>{undefined
console.log(props)
},[]) //仅在挂载和卸载的时候执行
3.传递一个值(对应值更新时回调)
useEffect(()=>{undefined
console.log(count)
},[count]) //count更新时执行
4.传递多个
const Asynchronous : React.FC=({number})=>{undefined
const [number2,setNumber2] = useState(number);
useEffect(()=>{undefined
console.log(number)
setNumber2(number)
},[number,setNumber2]) //监听props对象number的更改
//setNumber2是useState返回的setter,所以不会在每次渲染时重新创建它,因此effect只会运行一次
}
5.return 方法
const timer = setInterval(() => {undefined
setCount(count + 1)
}, 1000)
// useEffect方法的第一个参数是一个函数,函数可以return一个方法,这个方法就是在组件销毁的时候会被调用
useEffect(() => {undefined
return () => {undefined
clearInterval(timer)
}
}, [])
在vue组件通信中,跨组件通信的手段极其丰富且灵活,常用的通信方案有父子组件通信、ref通信、事件总线、provide/inject、parent/children、listeners/attrs、slot插槽通信等。除此之外,在vue中还可以使用vuex 或 mobx 来实现跨组件通信。总体上来讲,vue的组件通信极其灵活,自上而下、自下而上都是容易实现的;也正是因为过于灵活,这会“诱惑”开发者容易滥用通信手段,导致vue项目呈现出“易开发、难维护”的现状。在react中数据是单向数据流,在组件树中数据只能自上而下地分发和传递。state是组件自有的状态数据,props是父级组件传递过来的数据。在react中最最基本的通信方案是状态提升,还有React上下文也可以实现自上而下的数据流。鉴于react这种数据流的特性,即使集成了Redux仍然会呈现出单向数据流的特征,因此React数据流更容易被管理,配合Redux一起更适合做中大型的项目开发。
一个简单版本的vue,需要实现Compiler类(用于模板编译)、Watcher类(用于更新视图)、Dep类(用于依赖收集)、Observer类(用于数据劫持)、Vue类(构造函数)等。
React采用了最新的Fiber(双向链表)的数据结构,作为“协调”(Diff)运算的基础数据。如果要手写一个简易版本的React,其核心要实现以下功能,createElement(用于创建元素)、createDOM/updateDOM(用于创建和更新DOM)、render/workLoop(用于生成Fiber和协调运算)、commitWork(用于提交)等,如果还有支持Hooks,还得封闭Hooks相关的方法。
React Hooks: 就是用函数的形式代替原来的继承类的形式,并且使用预函数的形式管理state,有Hooks可以不再使用类的形式定义组件了。
为什么React要求hook的调用顺序不能改变(不能在条件语句中使用hook) —— 每次render时都是从一条固定顺序的链表中获取hook对应数据的。
hooks保存在currentlyRenderingFiber.memoizedState中保存一条hook对应数据的单向链表。
例:
const hookA = {
// hook保存的数据
memoizedState: null,
// 指向下一个hook
next: hookB
// …省略其他字段
};
hookB.next = hookC;
currentlyRenderingFiber.memoizedState = hookA;
useState返回值数组第二个参数为改变state的方法。在源码中,他被称为dispatchAction。
多次调用dispatchAction会形成一条环状链表。这条链表显然属于该useState hook。
update3 –next–> update1
^ |
| update2
|next_|
hooks useState数据
const hook = {
// hook保存的数据
memoizedState: null,
// 指向下一个hook
next: hookForB
// 本次更新以baseState为基础计算新的state
baseState: null,
// 本次更新开始时已有的update队列
baseQueue: null,
// 本次更新需要增加的update队列
queue: null,
};
react:
解决CPU瓶颈的关键是实现时间切片,而时间切片的关键是:将同步的更新变为可中断的异步更新。
Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
Reconciler(协调器)—— 负责找出变化的组件
Renderer(渲染器)—— 负责将变化的组件渲染到页面上
代数效应是函数式编程中的一个概念,用于将副作用从函数调用中分离。
Fiber并不是计算机术语中的新名词,他的中文翻译叫做纤程,与进程(Process)、线程(Thread)、协程(Coroutine)同为程序执行过程。
在很多文章中将纤程理解为协程的一种实现。在JS中,协程的实现便是Generator。
所以,我们可以将纤程(Fiber)、协程(Generator)理解为代数效应思想在JS中的体现。
React Fiber可以理解为:
React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态。
什么是“双缓存”
我们可以在内存中绘制当前帧动画,绘制完毕后直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到出现画面的闪烁情况。
React使用“双缓存”来完成Fiber树的构建与替换——对应着DOM树的创建与更新。
1 | title={`回放-${videoList[itemIndex].createdTime} ${videoList[itemIndex].airplaneCode} ${videoList[itemIndex].videoName}`}> |