# relearn-react **Repository Path**: jakequc/relearn-react ## Basic Information - **Project Name**: relearn-react - **Description**: Relearn the react series - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-04-09 - **Last Updated**: 2023-04-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Getting Started with Create React App ## 重学 react ```ts type VirtualDom = { // 这个决定了 virtualDom 以怎样的方式渲染成为 真实 dom type?: any; key: React.Key; ref: any; props: { children?: string | VirtualDom; ...otherProps }; }; // 一个 virtual dom 大概长成这个样子 const vdom = { key: null, ref: null, props: { children: [ { type: "h2", key: null, ref: null, props: { clsss: "h2", style: { background: "#f00", }, children: "h2标签", }, _owner: null, _store: {}, }, { type: "div", key: null, ref: null, props: { className: "box", children: [ { type: "span", key: null, ref: null, props: { children: 1, }, _owner: null, _store: {}, }, { type: "span", key: null, ref: null, props: { children: 2, }, _owner: null, _store: {}, }, ], }, _owner: null, _store: {}, }, ], }, _owner: null, _store: {}, }; ``` 将 jsx 渲染到 浏览器(真实 dom)的过程: 首选 react 会将 jsx 基于 babel-preset-react-app 的 "react/jsx-runtime"(即 React.createElement) 包 生成 virtualDOM(就像上边一样),然后由 ReactDOM.render 方法 根据 virtualDOM 和 container 将 virtualDOM 渲染为真实的 dom, render 的过程如下: 1. 如果是第一次,直接根据 jsx 生成 virtualDom,根据 virtualDom 中的 type 来得到不同的 dom 过程 1.1 如果 type 是字符串(假定为 原生标签) 则使用 document.createElement(type) 来生成 dom 插入到 container 中(中间涉及了递归的过程) 1.2 如果 type 是普通函数(函数组件),则将 VirtualDOM 的 props 传递给该函数获并运行改函数取到其内部的 jsx,然后在基于 babel-preset-react-app 的 react/jsx-runtime(React.createElement)生成 virtualDOM, 然后在去执行 1.1 的步骤(将该函数的执行结果传递给 render 的第一个参数) 1.3 如果 type 是一个构造函数(类组件),把构造函数基于 new 执行创建一个类的实例(会将解析出来的 props 传递过去),每次调用类组件都会创建一个单独的实例,会把类组件中编写的 render 函数执行返回的 jsx(virtualDOM)当作组件视图进行渲染) 1.3.1 实例化类组件内部发生的事情 - 初始化属性 & 规则校验 constructor(props) { super(props); // 会把传递进来的属性挂在到 this 实例上 } - 如果不在 constructor 中处理 props 或者没有写 constructor, React 内部也会把传递的 props 挂载到实例上,所以在 其他函数中,只要保证 this 是实例,就可以基于 this.props 获取传递的属性(props 也是只读的,因为 使用了 Object.freeze(this.props); 2. 如果是非首次挂在,则将 jsx 的最新结果与 原来的 virtualDom 进行对比,对比之后生产 patch, 将 patch 更新到 dom 上即可 ### 基于 extends 实现继承 > React 中的类组件 > class ClassComonentName extends React.Component {}; 1. 首先基于 call 继承 React.Component.call(this) function Component(propts,context,updater) { this.props = props; this.context = context; this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; } // 给函数组件创建的实例设置四个私有属性: props/context/refs/updater 2. 再基于原型继承 ClassComponent.prototype.**proto** === React.Component.prototype 实例除了具备 ClassComponent.prototype 提供的方法外,还具备了 React.Compnoent.prototype 原型上提供的方法: isReactComponent, setState, forceUpdate ### 类组件一般需要做的事情 1. 初始化属性 && 规则校验 - 先规则校验(tsx 的 type / interface) 或者 prop-types 来 借助 static defaultProps / propTypes 来进行规则校验 2. 初始化状态 & 修改视图更新 状态: 后期修改状态,可以出视图更新,如果不需要触发试图更新的数据就不需要放在变量里边,实例上挂载的 state 默认是 null 修改视图更新 - 通过 this.setState(partialState) 进行状态更新,让视图更新 - 还有一个强制更新的方法 this.forceUpdate(); 强制更新试图 (不推荐) 钩子函数:在程序运行到某个阶段,可以基于提供的处理函数,让开发者在这个阶段做一些自定义更新的事情 3. 第一次挂在组件执行的顺序 (componentWillMount => render => componentDidMount) - componentWillMount 周期函数(钩子函数,该生命周期函数不建议使用,使用时可添加 UNSAFE\_ 前缀表示该函数不安全) - render 周期函数,渲染视图内容 - componentDidMount 组件第一次渲染完毕,页面中已经有真实创建的真实 DOM (所以我们可以获取真实 DOM - 如果开启了 React.StrictMode (React 的严格模式),使用 UNSAFE_xxx 这样的生命周期控制台会抛出红色警告错误!! - React.StrictMode VS "use strict" - "use strict": JS 的严格模式 - React.StrictMode 检查 React 中一些不规范的语法或不安全的或不建议使用的 API 等 ```jsx import PropTypes from "prop-types"; class ClassComponent extends React.Component { // 属性规则校验 static defaultProps = { num: 0, }; static propTypes = { title: PropTypes.string.isRequired, num: PropTypes.number, }; xxxx; render() { return something; } } ``` ### 类组件更新的逻辑「当修改了相关状态,组件会更新) > - 使用 setState 更新当前组件的顺序 shouldComponentUpdate => UNSAFE_componentWillUpdate(static getSnapshortBeforeUpdate) => render => componentDidUpdate > - 父组件更新导致子组件更新 顺序: 父 shouldComponentUpdate => 父 UNSAFE_componentWillUpdate(static getSnapshortBeforeUpdate) => 父 render => 子组件 UNSAFE_componentWillReceiveProps(getDerivedStateFromProps) =》 子 shouldComponentUpdate => 子 UNSAFE_componentWillUpdate(static getSnapshortBeforeUpdate) => 子 render => 子 componentDidUpdate => 父 componentDidUpdate 父组件第一次渲染的原则是: 深度优先遍历 在父组件操作中,父组件先 willMount 然后遇到子组件,执行完子组件的渲染,父组件才能继续 render 等其他生命周期 父组件更新流程原则: 父组件 执行 shouldComponentUpdate -> 父 render -> 子组件 UNSAFE_componentWillReceiveProps 废弃,使用 static getDerivedStateFromProps 代替) 父组件的销毁: 父组件触发 componentWillUnmount -> 触发子组件的 componentWillUnmount -> 子组件销毁 -> 父组件销毁 1. 触发 shouldComponentUpdate(nextProps,nextState), 可以根据最新的 props (nextProps) 和 最新的 state(nextState) 和 当前的 this.state(更新前的 state) 和 this.props(更新前的 props) 进行比较,返回是否更新的 boolean 值(false 不会进行更新 下边的几个函数将不会继续执行,true 需要更新) 2. UNSAFE_componentWillUpdate(nextProps,nextState) (废弃,使用 static getSnapshotBeforeUpdate 代替)执行一些将要更新的额外操作(更新之前) 3. 修改状态值,让 this.state 中的值变成最新的值 4. 触发 render 周期函数,组件更新: - 按照最新的状态把返回的 jsx 编译为 virtualDOM - 和第一次渲染出来的 virtualDOM 进行比对 DOM-diff - 把差异的部分进行渲染「渲染为真实的 DOM) 5. 触发 componentDidUpdate 周期函数: 组件更新完毕 特殊说明: 如果我们是基于 this.forceUpdate() 强制更新视图,会跳过 shouldComponentUpdate 函数的校验,直接会从 willUpdate 开始进行更新,也就是直接进行更新,视图一定会触发更新 ### 函数组件「推荐」 函数组件可以基于 hooks 函数来让函数组件拥有状态、生命周期函数等,让函数组件也可以拥有状态 ### React.PureComponent 和 React.Component 的区别 - PureComponent 会给 类组件默认加一个 shouldComponentUpdate 生命周期函数,因此当继承 PureComponent 之后不要自己写 shouldComponentUpdate ,它对新老的属性和状态做一个浅比较,如果经过浅比较属性和状态并没有改变,则返回 false 不继续更新组件,true 的时候才会去更新 - PureComponent 中 shouldComponentUpdate 的(state\props)浅比较,只比较对象的第一级,对于深层次的内容,不会进行比较 (如果是对象会比较地址,地址一样默认相同,然后会较对象的数量,如果数量不一致表示不相同) ``` // 检测是否为对象 const isObject = (obj) => { return obj !== null && /^(object|function)$/.test(typeof obj); }; // 浅比较对象的方法 const shallowEqual = (objA, objB) => { if (!isObject(objA) || !isObject(objB)) return false; if (objA === objB) return true; // 比较成员的数量 const keysA = Reflect.ownKeys(objA), keysB = Reflect.ownKeys(objB); // 长度不一样默认,return false if (keysA.length !== keysB.length) return false; // 数量一致,再逐一比较第一级(第一层的属性),浅比较 for (let i = 0; i < keysA.length; i++) { const key = keysA[i]; // 如果一个对象有一个属性在另一个对象中不存在,或者这个属性两个对象都存在但是值不一样,都视为不相等 if (!objB.hasOwnProperty(key) || !Object.is(objA[key], objB[key])) { return false; } } // 如果以上都处理完,发现没有不相同的成员,则认为两个对象都是相等的(第一层是相等的) return true; }; // shouldComponentUpdate(PureComponent) 中的默认实现 shouldComponentUpdate(nextPros,nextState) { const {props,state} = this.state; return !shallowEqual(props,nextProps) || !shallowEqual(state,nextState); } ``` ### 受控与非受控组件 受控组件: 基于修改数据/状态,让视图更新,达到需要的效果「推荐」 非受控组件: 基于 ref 获取 DOM 元素,操作 DOM 元素来实现需求 和效果 「偶尔」 类组件中的 ref 原理: 在 render 的时候,会获取 virtualDOM 的 ref 属性, 如果 ref 的值是一个字符串,则会给 this.refs 新增一个字符串的成员,成员就是当前的 DOM 元素;如果属性值是一个函数,则会把函数执行,把当前 DOM 元素传递给这个函数,而在函数执行的内部,我们一般会把 DOM 元素直接挂到实例的某个属性上 1. 在标签中
this.refName = dom}>xxx
, 获取 div 的 真实 dom this.refName (还有一种是 string 类型的 ref 不推荐) 2. refBox = React.createRef(); // 会在实例上挂在 refBox
xxx
// 会在 this.refBox.current 上挂在 div 的 真实 DOM - 给类组件设置 ref,目的获取当前组件的实例 - 给函数组件设置 ref 会报错,推荐使用 React.forwardRef 实现 函数组件内部 某些元素或方法 ### 类组件 this.setState(partialState,callback) - partialState(当传递的是对象): 支持部分状态更新,不论总共有多少个状态,指更改我们传递的 partialState,其余状态不动 - partialState(是函数的时候): setState((prevState) => ({})) - prevState 是之前的状态值 - return 的对象就是我们想要修改的心状态值「支持修改部分状态」 - callback: 在状态更改,视图更新完毕(componentDidUpdate)后触发执行,在 partialState 更新完之后处理一些 callback (不论 shouldComponentUpdate 组织了状态/视图的更新与否,callback 依旧会被触发执行),类似 Vue 框架中的 $nextTick 在 React18 中, setState 操作都是一步的 「不论是哪执行,如,合成事件、周期函数、定时器...」 - 目的: 实现状态的批处理【统一处理】 有效减少更新次数,降低性能消耗、有效管理代码执行的逻辑顺序 - 原理: 利用了更新队列「updater」机制来处理 - 在当前相同时间段内「浏览器此时可以处理的事件中」,遇到 setState 会立即放到更新队列当中 - 此时状态/视图还未更新 - 所有代码操作结束,会通知更新队列中的任务执行:把所有放入的 setState 合并在一起执行,只触发一次视图更新【批处理操作】 在 React 16 和 React18 ,有关 setState 同步还是异步,是有一些区别的 - 在 React18 中, setState 都是基于 updater 更行队列机制,实现批处理的(都是异步的) - 在 React16 中,如果在合成事件(jsx 元素中基于 onXX 绑定的事件」、函数周期中,setState 的操作都是异步的!,但是在其他异步操作中(如 定时器、手动获取 DOM 元素做的事件保定等」他将变成同步操作(遇到 setState 会立即更新状态让视图渲染) ### 手动刷新队列 react-dom 中有 flushSync 方法,可以刷新当前的状态更新队列,实现状态渲染 当需要让前边的状态先渲染到页面后,再让另外的状态渲染可以将 flushSync(callback?:Function) 传递一个 callback,callback 中去 setState, 也可以在 setState 后加 flushSync ### React 中的合成事件 Synthetic(合成事件)是围绕浏览器原生事件,充当跨浏览器包装器的对象,他们将不同浏览器的行为合并为一个 API,这样做是为了确保事件在不同浏览器中显示一致的行为 #### 基础语法 在 jsx 元素上,onXxx={function} 进行事件绑定