jsx
- JSX其实只是一种语法糖,最终会通过babeljs转译成
React.createElement
语法 React.createElement
会返回一个React元素- React元素事实上是普通的JS对象,用来描述你在屏幕上看到的内容
ReactDOM
来确保浏览器中的真实DOM数据和React元素保持一致
babel并没有把jsx编译成虚拟dom,而是把jsx编译成React.createElement的方法调用,在浏览器执行的时候才会执行React.createElement,才会生成虚拟dom
jsx的实现
"start": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts start",
- DISABLE_NEW_JSX_TRANSFORM 不开启新的jsx转换
- jsx转换器做了改变
- react17之前 会转换成React.createElement
- react17之后
import {jsx as _jsx} from 'react/jsx-runtime'
let element = _jsx(...)
- _jsx 就相当于之前的 React.createElement
- 原因是: 在编写代码的时候,如果不引入React, 因为编译之后会用到React.createElement,所以运行代码的时候会报错;如果引入React,因为在代码编写的时候实际上并没有用到React这个变量,有些工具如eslint会报错。这就有点矛盾。所以在react17之后做了改变,只要使用了jsx,编译时会自动引入'react/jsx-runtime'这个包,这样就不再需要在代码中引入React了
createElement方法
- 创造一个虚拟dom,也就是一个react元素
- 参数:
- type, 元素的类型 span/p/div...
- config, 配置对象 className style...
- chidlren... 儿子 可能一个可能多个
- 编译阶段 ref/key两个属性会放在config里, 最终会将ref/key两个属性放到元素上
- ref 引入此元素
- key 唯一标识一个元素
- 返回一个对象 { type, ref, key, props} (children放在props里面)
render方法
- 根据虚拟dom创建真实dom,并更新属性, 挂载到container上
- 再处理儿子虚拟dom,同样创建真实dom,更新属性,挂载到父元素上
- 同步递归创建
组件
- 函数组件
- 类组件
- 定义状态有两种写法
-
- 在构造函数中 this.state = {}
-
- 直接在类中 state = {} 类的属性 这两种写法是等效的
-
- state的更新可能是异步的,处于性能的考虑react可能会把多个setState合并成同一个调用
- 在事件处理函数中,setState的调用会批量执行。setState并不会修改this.state,等事件处理函数结束后再进行更新
- setState里面可以放置函数 当放置函数的时候,还是批量更新,两次打印出来的值还是0,但是,但是最终this.state.number = 2。因为函数会把老的状态作为参数,返回一个新状态,作为下一个函数的老状态
- 在其他react不能管控的地方 setState就是同步执行(如setTimeout,原生dom事件,setInterval等)
- 在react能管控的地方就是批量异步更新(如事件处理函数,生命周期函数)
- 定义状态有两种写法
handleClick = () => {
this.setState({
number: this.state.number + 1,
});
console.log(this.state); // 0
this.setState({
number: this.state.number + 1,
});
console.log(this.state); // 0
}
// state里的number变为1
// setState执行多次 每次用的都是原始的state的值,因为没有立即更改
handleClick = () => {
this.setState(state =>({
number: state.number + 1,
}));
console.log(this.state); // 0
this.setState(state =>({
number: state.number + 1,
}));
console.log(this.state); // 0
}
handleClick = () => {
this.setState({
number: this.state.number + 1,
});
console.log(this.state); // 0
this.setState({
number: this.state.number + 1,
});
console.log(this.state); // 0
// 当前执行栈就结束了 setTimeout是下一个宏任务 所以会做一次批量更新 this.state.number变成1
setTimeout(() => {
this.setState({
number: this.state.number + 1,
});
console.log(this.state); // 2
this.setState({
number: this.state.number + 1,
});
console.log(this.state); // 3
})
}
- 每个组件都有一个更新器 Updater实例
- 会将setState里的对象或者函数保存在一个队列
- 将回调也保存在一个队列
class Updater {
constructor(classInstance) {
this.classInstance = classInstance; //组件实例
this.pendingStates = []; //
this.callbacks = [];
}
addState(partialState, callback) {
this.pendingStates.push(partialState);///等待更新的或者说等待生效的状态
if (typeof callback === 'function')
this.callbacks.push(callback);//状态更新后的回调 回调也放入数组
this.emitUpdate();
}
emitUpdate() {
this.updateComponent();
}
updateComponent() {
let { classInstance, pendingStates } = this;
if (pendingStates.length > 0) {
shouldUpdate(classInstance, this.getState());
}
}
getState() {
let { classInstance, pendingStates } = this;
let { state } = classInstance;
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state);
}
state = { ...state, ...nextState };
});
pendingStates.length = 0;
return state;
}
}
function shouldUpdate(classInstance, nextState) {
classInstance.state = nextState;
classInstance.forceUpdate();
}
export class Component {
static isReactComponent = true;
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback); // 更新状态加到数组中
}
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom; // 上一次类组件render方法得到的虚拟dom
let oldDOM = findDOM(oldRenderVdom); // 上一次虚拟Dom对应的真实dom
let newRenderVdom = this.render();
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
}
}
合成事件与批量更新
- 采用小驼峰 onClick onMouseMove
- 原生事件里传函数名字符串 在react中传一个函数的引用地址 真实的函数定义
- 为了判断是批量异步更新还是同步更新, 引入了一个全局变量 updateQueue更新队列
- 在事件函数执行之前 把
isBatchingUpdate= true
- 事件函数本身代码逻辑执行 setState 都添加到了数组当中
- 事件函数执行完成之后 调用
updateQueue.batchUpdate
批量更新方法,在方法中会再次将isBatchingUpdate = false
- 在事件函数执行之前 把
- 为了在事件函数执行之前将
isBatchingUpdate= true
,就用到了合成事件
合成事件
- 合成事件的实现原理是事件委托
- 如要向button上绑定click事件,实际不向button上进行绑定,而是绑定到document上
- 这样做的好处是
-
- 可以实现在事件开始的时候设置
isBatchingUpdate= true
,结束的时候注入批量更新的逻辑
- 可以实现在事件开始的时候设置
-
- 可以做一些浏览器的兼容性处理。不同浏览器的api不一样,把不同的事件对象处理成处理成标准化的事件对象,提供标准化的api供用户使用
-
- 事件委托
- react17之前,事件都委托到了document文档对象
- react17之后,事件都委托到了容器上
- 这样做是为了在一个页面上可以存在多个react应用(以前的方式是不行的)
<div id="root1"/> ReactDDOM.render(<h1/>, root1);
<div id="root2"/> ReactDOM.render(<h2/>, root2);
- 给元素绑定事件函数的时候执行的是addEvent方法
- store是在要绑定事件函数的dom上的一个自定义属性 用于存放这个dom都绑定了哪些事件以及对应的函数
- 如果很多元素都绑定了onClick事件,只会往文档对象document上绑一次onClick
ref
为dom添加ref
- 可以使用 ref 去存储 DOM 节点的引用
- 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性
为 class 组件添加 Ref
- 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性
Ref转发
- 你不能在函数组件上使用 ref 属性,因为他们没有实例
- Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧
- Ref 转发允许某些组件接收 ref,并将其向下传递给子组件
- 通过
React.forwardRef
可以将ref在函数组件中作为参数向下传递,使用在原生dom上