发布一个npm包

注册账号

npm注册 记得要去邮箱确认,不然发布的时候回报错

也可以使用命令行的方式

1
2
3
4
npm adduser

#注册并登录
npm login

登录成功提示 Logged in as xxx on https://registry.npmjs.org/.

创建一个包

一个最小的包,只需要一个package.json文件,可以用npm init来生成这个文件

1
2
3
4
5
6
7
8
9
10
11
{
"name": "@noopn/log",
"version": "0.0.1",
"main": "index.js",
"keywords": [
"log",
"logger",
"console"
],
"license": "MIT"
}

其中有一些字段是必须的:

name: 包的名字,你可能看到我用了 @noopn/log 这样的名字,这表示会创建一个在我们用户名 scope(作用范围) 下的一个包。这个叫做 scoped package。它允许我们将已经被其他包使用的名称作为包名,比如说,log 包 已经在 npm 中存在。比如 @angular/core@angular/http

version: 版本号,以便开发人员在安全地更新包版本的同时不会破坏其余的代码。npm 使用的版本系统被叫做 SemVer,是 Semantic Versioning 的缩写。

给定版本号 MAJOR.MINOR.PATCH,增量规则如下:
MAJOR 版本号的变更说明新版本产生了不兼容低版本的 API 等,
MINOR 版本号的变更说明你在以向后兼容的方式添加功能
PATCH 版本号的变更说明你在新版本中做了向后兼容的 bug 修复.

发布

现在准备好可以使用 npm publish 发布了,但不兴的是会得到一个错误

1
2
3
npm ERR! publish Failed PUT 402
npm ERR! code E402
npm ERR! You must sign up for private packages : @noopn/log

Scoped packages 会被自动发布为私有包,因为这样不但对我们这样的独立用户有用,而且它们也被公司用于在项目之间共享代码。我们想让每个人都可以使用这个模块.使用下面这个命令:

1
npm publish --access=public

成功之后会看见 + @noopn/log@0.0.1

渐入佳境

虽然发布了我们的第一个包,但是现在还不能向别人展示我们的代码,那这个地方就是github

首先新建一个项目

让我们把本地项目和远程项目关联起来

1
2
3
4
5
6
7
8
# 跟踪新文件,或者说将内容从工作目录添加到暂存区
git add .
# 将暂存区内容添加到本地仓库中。
git commit -m "xx"
# 拉去远程代码
git pull origin master
# 提交代码
git push -u origin master

在添加了新的内容之后,升级一下包的版本,并重新发布

1
2
npm version batch
npm publish

React原理 state深入

同步还是异步

batchUpdate批量更新可能并不准确,React 是有多种模式的,基本平时用的都是 legacy 模式下的 React,除了legacy 模式,还有 blocking 模式和 concurrent 模式, blocking 可以视为 concurrent 的优雅降级版本和过渡版本,React 未来将以 concurrent 模式作为默认版本,这个模式下会开启一些新功能。

对于 concurrent 模式下,会采用不同 State 更新逻辑。前不久透露出未来的React v18 版本,concurrent 将作为一个稳定的功能出现。

setState时候发生了什么

  • 首先,setState 会产生当前更新的优先级(老版本用 expirationTime ,新版本用 lane )。

  • 接下来 React 会从 fiber Root 根部 root fiber 向下调和子节点,调和阶段将对比发生更新的地方,更新对比 expirationTime ,找到发生更新的组件,合并 state,然后触发 render 函数,得到新的 UI 视图层,完成 render 阶段。

  • 接下来到 commit 阶段,commit 阶段,替换真实 DOM ,完成此次更新流程。

  • 接下来会执行 setStatecallback 函数,如上的()=>{ console.log(this.state.number) },到此为止完成了一次 setState 全过程。

对更新的限制

① pureComponent 可以对 state 和 props 进行浅比较,如果没有发生变化,那么组件不更新。

② shouldComponentUpdate 生命周期可以通过判断前后 state 变化来决定组件需不需要更新,需要更新返回true,否则返回false。

实现原理

setState实际上调用了Component上的updater对象的类方法

/react-reconciler/src/ReactFiberClassComponent.new.js

1
2
3
4
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const classComponentUpdater = {
enqueueSetState(inst, payload, callback) {
// 获取当前fiber节点
const fiber = getInstance(inst);
// 获取当前更新时间
const eventTime = requestEventTime();
// 获取更新优先级
const lane = requestUpdateLane(fiber);

// 每一次调用`setState`,react 都会创建一个 update
const update = createUpdate(eventTime, lane);
update.payload = payload;

// 保存更新之后的会掉函数
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
/* enqueueUpdate 把当前的update 传入当前fiber,待更新队列中 */
enqueueUpdate(fiber, update, lane);

// 开始调度更新
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}

批量更新在何时处理?

大部分的更新都是由UI交互产生,或异步的方法和函数,例如setTimeoutxhr,批量更新和事件系统息息相关

事件系统的函数调用过程为:

/react-dom/src/client/ReactDOMRoot.js

1
2
3
4
5
6
7
8
export function hydrateRoot(
container: Container,
initialChildren: ReactNodeList,
options?: HydrateRootOptions,
): RootType {
// 在root元素上监听所有的时间
listenToAllSupportedEvents(container);
}
1
2
3
4
5
6
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
// 循环所有的事件名称,绑定事件
allNativeEvents.forEach(domEventName => {
listenToNativeEvent(domEventName, true, rootContainerElement);
});
}
1
2
3
function listenToNativeEvent(domEventName, isCapturePhaseListener, rootContainerElement, targetElement) {
addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
}
1
2
3
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) {
var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags); // If passive option is not supported, then the
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {
var eventPriority = getEventPriorityForPluginSystem(domEventName);
var listenerWrapper;
switch (eventPriority) {
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;

case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate;
break;

case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}
1
2
3
4
function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, null, targetContainer);
} // Attempt dispatching an event. Returns a SuspenseInstance or Container if it's blocked.

1
2
3
4
5
6
// 在`legacy`模式下,所有的事件都将经过此函数同一处理 
function dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
batchedEventUpdates(function () {
return dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, ancestorInst);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
function batchedEventUpdates(fn, a, b) {
// 标记为批量更新
// scheduleUpdateOnFiber中会根据这个变量判断是否批量更新
isBatchingEventUpdates = true;
try {
return batchedEventUpdatesImpl(fn, a, b);
} finally {
// try 不会影响finally执行,执行结束后标记为false
isBatchingEventUpdates = false;
finishEventHandler();
}
}

更新调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default class index extends React.Component{
state = { number:0 }
handleClick= () => {
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback1', this.state.number) })
console.log(this.state.number)
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback2', this.state.number) })
console.log(this.state.number)
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback3', this.state.number) })
console.log(this.state.number)
}
render(){
return <div>
{ this.state.number }
<button onClick={ this.handleClick } >number++</button>
</div>
}
}

最终打印的结果是 0,0,0,callback1 ,1,callback2 ,1,callback3 ,1,

如果是异步执行,调用栈会被改变

1
2
3
4
5
6
7
8
setTimeout(()=>{
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback1', this.state.number) })
console.log(this.state.number)
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback2', this.state.number) })
console.log(this.state.number)
this.setState({ number:this.state.number + 1 },()=>{ console.log( 'callback3', this.state.number) })
console.log(this.state.number)
})

在异步环境批量更新

1
2
3
4
5
6
7
8
9
10
11
12
import ReactDOM from 'react-dom'
const { unstable_batchedUpdates } = ReactDOM
setTimeout(()=>{
unstable_batchedUpdates(()=>{
this.setState({ number:this.state.number + 1 })
console.log(this.state.number)
this.setState({ number:this.state.number + 1})
console.log(this.state.number)
this.setState({ number:this.state.number + 1 })
console.log(this.state.number)
})
})

那么如何提升更新优先级呢?

React-dom 提供了 flushSync ,flushSync 可以将回调函数中的更新任务,放在一个较高的优先级中。React 设定了很多不同优先级的更新任务。如果一次更新任务在 flushSync 回调函数内部,那么将获得一个较高优先级的更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
handerClick=()=>{
setTimeout(()=>{
this.setState({ number: 1 })
})
this.setState({ number: 2 })
ReactDOM.flushSync(()=>{
this.setState({ number: 3 })
})
this.setState({ number: 4 })
}
render(){
console.log(this.state.number)
return ...
}

最终结果打印 3,4,1

flushSync补充说明:flushSync 在同步条件下,会合并之前的 setState | useState,可以理解成,如果发现了 flushSync ,就会先执行更新,如果之前有未更新的 setState | useState ,就会一起合并了,所以就解释了如上,2 和 3 被批量更新到 3 ,所以 3 先被打印。

综上所述, React 同一级别更新优先级关系是:

flushSync 中的 setState > 正常执行上下文中 setState > setTimeout ,Promise 中的 setState。

hooks中的state

行为与类中的相似, 需要注意的是,在一个方法中的执行上下文中,是获取不到最新的state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export default function Index(props){
const [ number , setNumber ] = React.useState(0)
/* 监听 number 变化 */
React.useEffect(()=>{
console.log('监听number变化,此时的number是: ' + number )
},[ number ])

const handerClick = ()=>{
// 遇到下面高优先级更新被合并更新
setNumber(5)

// 和handerClick方法中下面的几个打印函数一样
// 打印值都为0,每次触发更新之后,Index函数都会被重新执行
// number值已经与当前环境绑定
console.log(number);

/** 高优先级更新 **/
ReactDOM.flushSync(()=>{
setNumber(3)
})

// 批量更新,只会触发一次更新
setNumber(1)
setNumber(2)
console.log(number);

// 滞后更新 ,批量更新规则被打破
setTimeout(()=>{
setNumber(4)
console.log(number);
})

};

// 每次函数被重新执行的时候,打印最新的state值
console.log(number)
return <div>
<span> { number }</span>
<button onClick={ handerClick } >number++</button>
</div>
}

相同与不同

相同

首先从原理角度出发,setState和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。

不同:

在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。

setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。

setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。

Ubuntu20.04安装VMwarePlayer

workstation 和 player之间有什么区别

前期准备

安装 build-essential

1
sudo apt install build-essential

下载 VMware Player

VMware Workstation Player

安装 VMware Player

添加可执行权限

1
sudo chmod a+x ./VMware-Player-xxx.bundle

安装

1
sudo ./VMware-Player-xxx.bundle

1
sudo sh VMware-Player-xxx.bundle

启动

按步骤安装

在BIOS中开启虚拟化技术

一般为BIOS中,【Configuration】选项下的【Intel Virtual Technology】

为 vmnet 和 vmmon 服务创建私有密钥

官方文档中解决找不到/dev/vmmon的问题

“Cannot open /dev/vmmon: No such file or directory” error when powering on a VM (2146460)

最后一步执行需要用root权限执行,并输入密码

1
2
sudo su
mokutil –import MOK.der

重启电脑,在UEFI BIOS中选择Enroll MOK

重启后就可以正常使用

实现一个plugin

通过plugin拷贝额外资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
const fs = require('fs');
const path = require('path');
const glob = require("glob")
const {promisify} = require('util');
const readFile = promisify(fs.readFile);

const {RawSource} = require('webpack').sources;
const { validate } = require('schema-utils');

const schema = {
"type": 'object',
"properties": {
"from": {
"type": 'string',
},
"to": {
"type": 'string',
},
"ignore": {
"type": 'array',
},
// 不允许有其他属性
"additionalProperties": false
}
};

class CopyfilePlugin {
constructor(options={}){
// 验证参数
validate(schema, options);
this.options = options;
}
apply(compiler) {
compiler.hooks.thisCompilation.tap('CopyfilePlugin',(compilation)=>{
compilation.hooks.additionalAssets.tapAsync('CopyfilePlugin', (callback) => {

const {from,to,ignore} = this.options;
// 获取系统运行时的文件目录
const {context} = compiler.options;
// 获取from的绝对路径
const fromRelative = path.resolve(context,from,'*.*');

// 获取from文件夹的文件
// 排除不需要的文件
const filePaths = glob.sync(fromRelative,{
ignore:['**/index.html']
})
filePaths.forEach(async (filePath)=>{
const file = await readFile(filePath);
// 获取文件名称
const filename = path.basename(filePath);
// 添加额外的打包资源
compilation.assets[filename] = new RawSource(file);
})

callback()
});
})
}
}

module.exports =CopyfilePlugin;

webpack中complation用法

Compilation

Compilation 模块会被 Compiler 用来创建新的 compilation 对象(或新的 build 对象)。 compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const {RawSource}  = require('webpack').sources;
const fs = require('fs');
const {promisify} = require('util');
const readFile = promisify(fs.readFile);
const path = require('path');

class Plugin1 {
apply(compiler) {
compiler.hooks.thisCompilation.tap('Plugin2',(compilation)=>{
// compilation也有自己的生命周期
// 可以对compilation对象上的资源进行操作
compilation.hooks.additionalAssets.tapAsync('Plugin2', async (callback) => {

const file = await readFile(path.resolve(__dirname,'../src/b.txt'));

// 添加额外的打包资源
compilation.assets['b.txt'] = new RawSource(file);
callback()
});
})
}
}

module.exports = Plugin1;

webpack中complier用法

tapable

tapbale暴露了很多钩子,可以为webpack插件创建时使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const {SyncHook,SyncBailHook,AsyncParallelHook,AsyncSeriesHook} = require('tapable')

class Lesson {
constructor(){
// 初始化hooks容器
// 数组中的参数为绑定钩子回调函数的字段描述
this.hooks = {
// 同步hooks,任务依次被执行
go:new SyncHook(['address']),
// 同步hooks,一旦其中一个有返回值,后面的hooks就停止执行
go1:new SyncBailHook(['address']),
// 异步钩子,并行执行
go2:new AsyncParallelHook(['name','age']),
// 异步钩子,并行串行
go3:new AsyncSeriesHook(['name','age']),
}
}
// 添加事件,可以同时绑定多个事件
tap(){
this.hooks.go1.tap('class',(address)=>{
console.log('class',address)
})
this.hooks.go1.tap('class1',(address)=>{
console.log('class1',address)
})

// 异步钩子的两种写法
this.hooks.go2.tapAsync('class3',(name,age,cb)=>{
setTimeout(() => {
console.log('class3',name,age);
cb()
},2000);
})

this.hooks.go2.tapPromise('class4',(name,age)=>{
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('class4',name,age);
},1000);
})
})
}
//触发hooks
start(){
this.hooks.go1.call('触发时间时候传入的参数')
this.hooks.go2.callAsync('Gavin',18)
}
}

const l = new Lesson();
l.tap();
l.start();

Compiler

Compiler 模块是 webpack 的主要引擎,它通过 CLI 传递的所有选项, 或者 Node API,创建出一个 compilation 实例。 它扩展(extend)自 Tapable 类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler 上注册。

在插件中使用不同的生命周期钩子来处理资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Plugin1 {
apply(compiler) {
compiler.hooks.initialize.tap('initialize',()=>{
console.log('编译器对象初始化')
})

compiler.hooks.emit.tapAsync(
'emit',
(compilation, callback) => {
setTimeout(() => {
console.log('出发emit事件');
callback();
}, 2000);
}
);
compiler.hooks.afterEmit.tapPromise(
'afterEmit',
(compilation, callback) => {
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('出发afterEmit事件')
}, 1000);
})
}
);
}
}

module.exports = Plugin1;

React原理 组件

class类组件

类组件继承了React.Component /react/src/ReactBaseClasses.js

类组件的updater对象在实例化的时候在被绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Component(props, context, updater) {
this.props = props; //绑定props
this.context = context; //绑定context
this.refs = emptyObject; //绑定ref
this.updater = updater || ReactNoopUpdateQueue; //上面所属的updater 对象
}
/* 绑定setState 方法 */
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
}
/* 绑定forceUpdate 方法 */
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
}

根据上面的源码,可以知道为什么类组件一定要在super函数中传入props

1
2
3
4
constructor(props){
super(props)
console.log(this.props) // 如果不传,打印 undefined 为什么?
}

执行super相当于执行Component函数如果没有传入props,Component函数不能绑定props类属性,在后面的子类中则取不到props

函数组件

注意:不要尝试给函数组件 prototype 绑定属性或方法,即使绑定了也没有任何作用,因为通过上面源码中 React 对函数组件的调用,是采用直接执行函数的方式,而不是通过new的方式

对于类组件来说,底层只需要实例化一次,实例中保存了组件的 state 等状态。对于每一次更新只需要调用 render 方法以及对应的生命周期就可以了。但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。

为了能让函数组件可以保存一些状态,执行一些副作用钩子,React Hooks 应运而生,它可以帮助记录 React 中组件的状态,处理一些额外的副作用。

组件常用的通信方式

  1. props 和 callback 方式

子组件可以调用父组件传递下来的方法,改变父组件的状态

  1. ref 方式。
  2. React-redux 或 React-mobx 状态管理方式。
  3. context 上下文方式。
  4. event bus 事件总线。

event bus的本质是观察者模式.

组件的强化方式

  • 类组件继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class BaseComponent extends React.Component{
constructor(props){
super(props)
this.state = {
name:"BaseComponent"
}
}
// 内部属性
interFunc (){
console.log('interFunc')
}
render(){
return <div>
基础组件 {this.state.name}
</div>
}
}

class ExtendsComponent extends BaseComponent {
// 重写父组件方法
interFunc(){
console.log('over write interFunc')
}
render(){
return <div>
{super.render()}
<p>ExtendsComponent</p>
</div>
}
}

组件的本质

组件的本质就是类和函数,只不过在函数和类上添加了渲染视图(render)或更新视图(setState/useState)的逻辑

react会像正常实例化类或执行函数一样处理组件

对于类组件的执行,是在react-reconciler/src/ReactFiberClassComponent.js中:

1
2
3
4
5
6
7
8
function constructClassInstance(
workInProgress, // 当前正在工作的 fiber 对象
ctor, // 我们的类组件
props // props
){
/* 实例化组件,得到组件实例 instance */
const instance = new ctor(props, context)
}

对于函数组件的执行,是在react-reconciler/src/ReactFiberHooks.js中

1
2
3
4
5
6
7
8
9
10
11
function renderWithHooks(
current, // 当前函数组件对应的 `fiber`, 初始化
workInProgress, // 当前正在工作的 fiber 对象
Component, // 我们函数组件
props, // 函数组件第一个参数 props
secondArg, // 函数组件其他参数
nextRenderExpirationTime, //下次渲染过期时间
){
/* 执行我们的函数组件,得到 return 返回的 React.element对象 */
let children = Component(props, secondArg);
}

初始化的时候会执行组件的函数 /react-reconciler/src/ReactFiberBeginWork.new.js

会按照不同的组件类型,调用不同组件声明的函数来初始化组件

1
2
3
4
5
6
7
8
function beginWork(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case IndeterminateComponent:
{
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
}
}

React原理 JSX基础

JSX是什么

JSX是一种JavaScript的语法扩展,运用于React架构中,其格式比较像是模版语言,但事实上完全是在JavaScript内部实现的。元素是构成React应用的最小单位,JSX就是用来声明React当中的元素,React使用JSX来描述用户界面。

1
2
3
const A = ()=>{
return <div id='root'></div>
}

最后JSX变成什么

显然JSX并不是标准的JS语法,需要转换成标准的JS语法浏览器才能识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function() {
React.createElement("div", {
style: {
height: 200
}
}, React.createElement("div", {
onClick: function(e) {
return console.log(e)
}
}), ["a", "b", "c"].map((function(t) {
return React.createElement("span", {
key: t
}, t)
}
)))
}

所有的JSX代码最终都会变成 React.createElement的函数调用

1
2
3
4
5
6
7
8
React.createElement({
// 组件类型 div等字符串
type,
// 一个对象,对应dom中的标签属性
[props],
// 子元素按原有顺序排列
[...children]
})

在老版本中由于编译后的代码会变成React.createElement的函数调用,所以必须在文件中引入React,17版以后

createElement 做了如下几件事

  • 处理config,把除了保留属性外的其他config赋值给props
  • 把children处理后赋值给props.children
  • 处理defaultProps
  • 调用ReactElement返回一个jsx对象(virtual-dom)

jsx对象上没有优先级、状态、effectTag等标记,这些标记在Fiber对象上,在mount时Fiber根据jsx对象来构建,在update时根据最新状态的jsx和current Fiber对比,形成新的workInProgress Fiber,最后workInProgress Fiber切换成current Fiber。

下面描述了JSX被转换之后,以及生成的type类型

1
2
3
4
5
6
7
8
9
10
<div style={{height:200}}>
<React.Fragment></React.Fragment> {/*Fragment组件*/}
string children {/*string类型子元素*/}
<FunctionComponent/> {/*函数式组件*/}
<ClassComponent/> {/*类组件组件*/}
{/*绑定属性*/}
<div onClick = {(e)=> console.log(e)}></div>
{/*数组类型子元素*/}
{['a','b','c'].map(item=> <span key={item}>{item}</span>)}
</div>

jsx元素类型 通过createElement转换 type属性
原生标签 react element类型 tagname 例如span div
fragment类型 react element类型 symbol react.fragment类型
文本类型 直接返回字符串
数组类型 返回数组,数组中的元素按上述规则转换
类组件 react element类型 类组件引用
函数组件 react element类型 函数组件引用
三元表达式 执行运算后的结果按上述规则匹配
函数调用 执行运算后的结果按上述规则匹配

React针对不同React element元素会产生不通的tag(种类),也就是不同的fiber对象, 在ReactWorkTags.js定义了tag种类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export const FunctionComponent = 0;              // 函数组件
export const ClassComponent = 1; // 类组建
export const IndeterminateComponent = 2; // 在不知道是函数组件还是类组件的时候
export const HostRoot = 3; // Root Fiber 必须呗包含在另一个节点中
export const HostPortal = 4; // 通过createPortal创建的fiber子树,可能是另一个渲染的入口
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8; // <React.StrictMode>
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12; // <Profiler />
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;

最终JSX会变成由Fiber节点组成的链表

数组结构中的子节点会作为Fragment的子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;

// Fiber
this.return = null; // 指向父级fiber节点
this.child = null; // 指向子级fiber节点
this.sibling = null; // 指向兄弟fiber节点
this.index = 0;

this.ref = null;

this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;

this.mode = mode;

// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;

this.lanes = NoLanes;
this.childLanes = NoLanes;

this.alternate = null;
}

对元素进行操作

  • React.Children.toArray 拍平子元素
1
2
3
4
5
6
7
8
9
10
11
12
const Element = (
<div style={{height:200}}>
<React.Fragment></React.Fragment>
string children
<FunctionComponent/>
<ClassComponent/>
<div onClick = {(e)=> console.log(e)}></div>
{['a','b','c'].map(item=> <span key={item}>{item}</span>)}
</div>
)

React.Children.toArray(Element.props.children)

  • 遍历子节点 React.Children.forEach React.Children.map

在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg。如果 children 是一个数组,它将被遍历并为数组中的每个子节点调用该函数。如果子节点为 null 或是 undefined,则此方法将返回 null 或是 undefined,而不会返回数组。

如果 children 是一个 Fragment 对象,它将被视为单一子节点的情况处理,而不会被遍历。

1
React.Children.map(children, function[(thisArg)])
  • React.Children.only

验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。

1
React.Children.only(children)
  • React.cloneElement 克隆元素

以 element 元素为样板克隆并返回新的 React 元素。config 中应包含新的 props,key 或 ref。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,如果在 config 中未出现 key 或 ref,那么原始元素的 key 和 ref 将被保留。

1
2
3
4
5
React.cloneElement(
element,
[config],
[...children]
)

React.cloneElement() 几乎等同于:

1
<element.type {...element.props} {...props}>{children}</element.type>

但是,这也保留了组件的 ref。这意味着当通过 ref 获取子节点时,你将不会意外地从你祖先节点上窃取它。相同的 ref 将添加到克隆后的新元素中。如果存在新的 ref 或 key 将覆盖之前的。

js文件上传

在web应用程序中使用文件

选择文件

1
<input type="file" id="input">
1
const selectedFile = document.getElementById('input').files[0];

通过事件选择文件

1
2
3
document.getElementById('input').addEventListener('change',function(e){
const file = this.files[0]
})

上传选择的文件

1
2
3
4
5
6
7
8
const input = document.getElementById('input')
const fd = new FormData();
fd.append('data', input.files[0]);

fetch('http://xxx.xxx.xxx', {
method: 'POST',
body:fd
}).then(res => res.json())

创建文件并上传

Blob

1
2
3
4
5
6
7
8
9
const input = document.getElementById('input')
const fd = new FormData();
const blob = new Blob(["[]"], { type: 'plain/text' });
fd.append('data', blob,'script.txt');

fetch('http://xxx.xxx.xxx', {
method: 'POST',
body:fd
}).then(res => res.json())
  • Copyrights © 2015-2025 SunZhiqi

此时无声胜有声!

支付宝
微信