npm i create-react-app -g # 全局安装react脚手架
create-react-app react2022 # 使用react脚手架创建项目
cd react2022 # 进入项目
yarn start # 运行项目
-
react.js React 核心库-
react-dom.js 提供 DOM 相关的功能<h1 className="title" style={{color:'red'}}>react</h1>
React.createElement("h1", {
className: "title",
style: {
color: 'red'
}
}, "react");
createElement 的结果
{
type:'h1',
props:{
className: "title",
style: {
color: 'red'
}
},
children:"react"
}
批量更新:为了提高性能,减少更新的次数,所以将一个事件函数中的更新进行合并
合成事件:如果 DOM 上绑定了过多的事件处理函数,整个页面响应以及内存占用可能都会受到影响。React 为了避免这类 DOM 事件滥用,同时屏蔽底层不同浏览器之间的事件系统差异,实现了一个中间层——SyntheticEvent,统一管控。
批量更新队列
export const updateQueue = {
isBatchingUpdate: false, //当前是否处于批量更新模式
updaters: new Set(), //当前更新队列中保存的所有的 updaters 实例,每个 updater 实例对应一个组件
batchUpdate() {
//批量更新的方法
updateQueue.isBatchingUpdate = false;
for (const updater of updateQueue.updaters) {
updater.updateComponent();
}
updateQueue.updaters.clear();
},
};
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];
this.callbacks = [];
}
addState(partialState, callback) {
// 把状态存起来
this.pendingStates.push(partialState);
if (callback) {
this.callbacks.push(callback);
}
this.emitUpdate();
}
emitUpdate(nextProps) {
this.nextProps = nextProps;
if (updateQueue.isBatchingUpdate) {
//如果当前处于批量更新模式,只添加updater实例到队列中,并不会进行实际的更新
updateQueue.updaters.add(this);
} else {
this.updateComponent();
}
}
}
export class Component {
static isReactComponent = REACT_COMPONENT;
constructor(props) {
this.props = props;
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {}
}
合成事件
/**
* 把新的属性同步到真实DOM上
* @param {*} dom
* @param {*} oldProps
* @param {*} newProps
*/
function updateProps(dom, oldProps = {}, newProps = {}) {
//处理新增和修改属性
for (const key in newProps) {
if (key === "children") {
continue; //儿子属性会单独处理,并不会在此处理
} else if (key === "style") {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
// 新增一个addEvent事件
addEvent(dom, key.toLowerCase(), newProps[key]);
} else {
//id className
dom[key] = newProps[key];
}
}
//处理删除属性
for (const key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
delete dom[key];
}
}
}
// 代理事件
export function addEvent(dom, eventType, handler) {
//读取dom._store_,一般来说肯定 是没有用
let store = dom._store_ || (dom._store_ = {});
//store.onclick= handleClick
store[eventType] = handler;
if (!document[eventType]) {
//document.onclick = dispatchEvent
document[eventType] = dispatchEvent;
}
}
// 委托给document文档对象的处理函数
function dispatchEvent(event) {
//事件名称 click 和事件源,就是你点的谁
const { type, target } = event;
const eventType = `on${type}`; //onclick
//在方法执行前把这个isBatchingUpdate设置为true
updateQueue.isBatchingUpdate = true;
let syntheticEvent = createSyntheticEvent(event);
let currentTarget = target;
//模拟的事件冒泡的过程
while (currentTarget) {
//分清楚 target 和 currentTarget
syntheticEvent.currentTarget = currentTarget;
let { _store_ } = currentTarget;
let handler = _store_ && _store_[eventType];
//handler拿 到的事件对象不是原生的事件而是React提供的合成事件对象1.可以处理兼容性
handler && handler(syntheticEvent);
if (syntheticEvent.isPropagationStopped) {
break;
}
currentTarget = currentTarget.parentNode;
}
//在方法结束后进行批量更新
updateQueue.batchUpdate();
}
function createSyntheticEvent(nativeEvent) {
let syntheticEvent = {};
for (let key in nativeEvent) {
let value = nativeEvent[key];
if (typeof value === "function") value = value.bind(nativeEvent);
syntheticEvent[key] = value;
}
syntheticEvent.nativeEvent = nativeEvent;
syntheticEvent.isPropagationStopped = false; //是否已经阻止了冒泡
syntheticEvent.isDefaultPrevented = false; //是否已经阻止了默认事件
syntheticEvent.preventDefault = preventDefault;
syntheticEvent.stopPropagation = stopPropagation;
return syntheticEvent;
}
function preventDefault() {
this.isDefaultPrevented = true;
let event = this.nativeEvent;
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
function stopPropagation() {
this.isPropagationStopped = true;
let event = this.nativeEvent;
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = false;
}
}
import React, {
Component,
useState,
useImperativeHandle,
useCallback,
useMemo,
useRef,
useEffect,
forwardRef,
useLayoutEffect,
} from "react";
import ReactDOM from "react-dom";
class Counter extends React.Component {
// 他会比较两个状态相等就不会刷新视图 PureComponent是浅比较
static defaultProps = {
name: "hello",
};
constructor(props) {
super();
this.state = { number: 0 };
console.log("1.constructor构造函数");
}
componentWillMount() {
// 取本地的数据 同步的方式:采用渲染之前获取数据,只渲染一次
console.log("2.组件将要加载 componentWillMount");
}
componentDidMount() {
console.log("4.组件挂载完成 componentDidMount");
}
handleClick = () => {
this.setState({ number: this.state.number + 1 });
};
// react可以shouldComponentUpdate方法中优化 PureComponent 可以帮我们做这件事
shouldComponentUpdate(nextProps, nextState) {
// 代表的是下一次的属性 和 下一次的状态
console.log("5.组件是否更新 shouldComponentUpdate");
return nextState.number % 2;
// return nextState.number!==this.state.number; //如果此函数种返回了false 就不会调用render方法了
} //不要随便用setState 可能会死循环
componentWillUpdate() {
console.log("6.组件将要更新 componentWillUpdate");
}
componentDidUpdate() {
console.log("7.组件完成更新 componentDidUpdate");
}
render() {
console.log("3.render");
return (
<div>
<p>{this.state.number}</p>
{this.state.number > 3 ? null : <ChildCounter n={this.state.number} />}
<button onClick={this.handleClick}>+</button>
</div>
);
}
}
class ChildCounter extends Component {
componentWillUnmount() {
console.log("组件将要卸载componentWillUnmount");
}
componentWillMount() {
console.log("child componentWillMount");
}
render() {
console.log("child-render");
return <div>{this.props.n}</div>;
}
componentDidMount() {
console.log("child componentDidMount");
}
componentWillReceiveProps(newProps) {
// 第一次不会执行,之后属性更新时才会执行
console.log("child componentWillReceiveProps");
}
shouldComponentUpdate(nextProps, nextState) {
return nextProps.n % 3 == 0; //子组件判断接收的属性 是否满足更新条件 为true则更新
}
}
ReactDOM.render(<Counter />, document.getElementById("root"));
// defaultProps
// constructor
// componentWillMount
// render
// componentDidMount
// 状态更新会触发的
// shouldComponentUpdate nextProps,nextState=>boolean
// componentWillUpdate
// componentDidUpdate
// 属性更新
// componentWillReceiveProps newProps
// 卸载
// componentWillUnmount
function updateChildren(parentDOM, oldVChildren, newVChildren) {
// 构建一个map
const keyOldMap={}
let lastPlacedIndex = 0
// 映射老虚拟DOM
oldVChildren.forEach((oldVChild, index) => {
keyOldMap[oldVChild.key || index] = oldVChild;
});
// 创建一个DOM补丁包,收集DOM操作
const patch = []
// 遍历新虚拟DOM
newVChildren.forEach((newVChild, index) => {
newVChild.mountIndex = index;
let oldVChild = keyOldMap[newVChild.key || index];
if (oldVChild) {
updateElement(findDOM(oldVChild).parentNode, oldVChild, newVChild);
if (oldVChild.mountIndex < lastPlacedIndex) {
patch.push({
type: MOVE,
oldVChild,
newVChild,
mountIndex: index,
});
}
//如果一个老节点被复用了,就可以从map中删除
delete keyOldMap[newVChild.key || index];
lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex);
} else {
patch.push({
type: PLACEMENT,
newVChild,
mountIndex: index,
});
}
});
//过滤出来补丁包里需要移动的老节点
let moveVChildren = patch
.filter((action) => action.type === MOVE)
.map((action) => action.oldVChild);
//先把那些要移动的和要删除节点先全部删除
Object.values(keyOldMap)
.concat(moveVChildren)
.forEach((oldVChild) => {
let currentDOM = findDOM(oldVChild);
currentDOM.remove(); //老的真实DOM B
});
patch.forEach((action) => {
let { type, oldVChild, newVChild, mountIndex } = action;
//老的真实子DOM节点集合
let childNodes = parentDOM.childNodes;
if (type === PLACEMENT) {
let newDOM = createDOM(newVChild);
let childNode = childNodes[mountIndex];
if (childNode) {
parentDOM.insertBefore(newDOM, childNode);
} else {
parentDOM.appendChild(newDOM);
}
} else if (type === MOVE) {
let oldDOM = findDOM(oldVChild);
let childNode = childNodes[mountIndex];
if (childNode) {
parentDOM.insertBefore(oldDOM, childNode);
} else {
parentDOM.appendChild(oldDOM);
}
}
}
}
Provider、Consumer 都是组件,Provider 是数据的发出者,Consumer 是数据的接收者,多用于第三方组件封装中。
import React, {Component} from 'react'
import Child from './children'
import {Provider} from "./context";
// 类组件
class App extends Component {
constructor(props) {
super(props)
this.state = {
name: 'aa'
}
}
render() {
const {data} = this.props
const {name}=this.state
return (<div>
<Provider value={data}>
{
data.map((item, index) => {
return <div key={index}>
<p>{item}</p>
</div>
})
}
<Child/>
</Provider>
</div>)
}
}
export default App
import React, {Component} from 'react'
import {Consumer} from "./context";
export default class Child extends Component {
constructor(props) {
super(props)
this.state = {
name: 'll'
}
}
render() {
console.log(3, '组件渲染虚拟dom')
return (<Consumer>{
// 接收Provider传过来的数据
(data) => {
console.log(data)
}
}</Consumer>)
}
}
function createContext() {
const context = {
$$typeof: REACT_CONTEXT,
_currentValue: undefined,
};
context.Provider = {
$$typeof: REACT_PROVIDER,
_context: context,
};
context.Consumer = {
$$typeof: REACT_CONTEXT,
_context: context,
};
return context;
}
function createDOM(vdom) {
let { type, props, ref } = vdom;
// 处理Provider类型标签
if(type.$$typeof === REACT_PROVIDER)
return mountProviderComponent(vdom);
// 处理Context类型标签
if(type.$$typeof === REACT_CONTEXT))
return mountContextComponent(vdom);
}
function mountProviderComponent(vdom) {
let { type, props } = vdom;
let context = type._context; //Provider._context
context._currentValue = props.value; //Provider是赋值
let renderVdom = props.children;
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountContextComponent(vdom) {
let { type, props } = vdom;
let context = type._context; //Consumer._context
let renderVdom = props.children(context._currentValue);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateElement(oldVdom, newVdom) {
if (oldVdom.type.$$typeof === REACT_CONTEXT)
updateContextComponent(oldVdom, newVdom);
if (oldVdom.type.$$typeof === REACT_PROVIDER)
updateProviderComponent(oldVdom, newVdom);
}
function updateProviderComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
if (!currentDOM) return;
let parentDOM = currentDOM.parentNode;
let { type, props } = newVdom;
let context = type._context;
context._currentValue = props.value;
let newRenderVdom = props.children;
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.oldRenderVdom = newRenderVdom;
}
function updateContextComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
if (!currentDOM) return;
let parentDOM = currentDOM.parentNode;
let { type, props } = newVdom;
let context = type._context;
let newRenderVdom = props.children(context._currentValue);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.oldRenderVdom = newRenderVdom;
}
场景介绍:
1.把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新 2.把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
使用方法:
// 子组件
function Child({ onButtonClick, data }) {
console.log("Child render");
return <button onClick={onButtonClick}>{data.number}</button>;
}
Child = memo(Child);
function App() {
const [number, setNumber] = useState(0);
const [name, setName] = useState("useMemo");
const addClick = useCallback(() => setNumber(number + 1), [number]);
const data = useMemo(() => ({ number }), [number]);
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Child onButtonClick={addClick} data={data} />
</div>
);
}
// useMemo
export function useMemo(factory, deps) {
if (hookStates[hookIndex]) {
let [lastMemoObj, lastDeps] = hookStates[hookIndex];
let same = deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++;
return lastMemoObj;
} else {
let newMemoObj = factory();
hookStates[hookIndex++] = [newMemoObj, deps];
return newMemoObj;
}
} else {
let newMemoObj = factory();
hookStates[hookIndex++] = [newMemoObj, deps];
return newMemoObj;
}
// useCallback
export function useCallback(callback, deps) {
if (hookStates[hookIndex]) {
let [lastCallback, lastDeps] = hookStates[hookIndex];
let same = deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++;
return lastCallback;
} else {
hookStates[hookIndex++] = [callback, deps];
return callback;
}
} else {
hookStates[hookIndex++] = [callback, deps];
return callback;
}
}
}
场景介绍:
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
useState 是 useReducer 的语法糖
使用方法:
const initialState = 0;
function reducer(state, action) {
switch (action.type) {
case "increment":
return { number: state.number + 1 };
case "decrement":
return { number: state.number - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
export function useReducer(reducer, initialState) {
if (!hookState[hookIndex]) {
hookState[hookIndex] = initialState;
// 将对应的hooks 下标和值缓存到当前挂载的实例
if (mountingComponent && !mountingComponent.hooks) {
mountingComponent.hooks = {};
mountingComponent.hooks[hookIndex] = hookState[hookIndex];
}
}
let currentIndex = hookIndex;
function dispatch(action) {
hookState[currentIndex] = reducer
? reducer(hookState[currentIndex], action)
: action;
scheduleUpdate();
}
return [hookState[hookIndex++], dispatch];
}
介绍:
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值
当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值
useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context
使用方法:
const CounterContext = React.createContext();
function reducer(state, action) {
switch (action.type) {
case "increment":
return { number: state.number + 1 };
case "decrement":
return { number: state.number - 1 };
default:
throw new Error();
}
}
// 子组件
function Counter() {
let { state, dispatch } = useContext(CounterContext);
return (
<>
<p>{state.number}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
function App() {
const [state, dispatch] = useReducer(reducer, { number: 0 });
return (
<CounterContext.Provider value={{ state, dispatch }}>
<Counter />
</CounterContext.Provider>
);
}
export function useContext(context) {
return context._currentValue;
}
介绍:
在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道
useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟类组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API
该 Hook 接收一个包含命令式、且可能有副作用代码的函数
使用方法:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0,
};
}
componentDidMount() {
document.title = `你点击了${this.state.number}次`;
}
componentDidUpdate() {
document.title = `你点击了${this.state.number}次`;
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button
onClick={() => this.setState({ number: this.state.number + 1 })}
>
+
</button>
</div>
);
}
}
// 在这个 class 中,我们需要在两个生命周期函数中编写重复的代码,这是因为很多情况下,我们希望在组件加载和更新时执行同样的操作。我们希望它在每次渲染之后执行,但 React 的 class 组件没有提供这样的方法。即使我们提取出一个方法,我们还是要在两个地方调用它。useEffect 会在第一次渲染之后和每次更新之后都会执行
import React, { Component, useState, useEffect } from "react";
import ReactDOM from "react-dom";
function Counter() {
const [number, setNumber] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `你点击了${number}次`;
});
return (
<>
<p>{number}</p>
<button onClick={() => setNumber(number + 1)}>+</button>
</>
);
}
ReactDOM.render(<Counter />, document.getElementById("root"));
// 每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect 属于一次特定的渲染。
export function useEffect(callback, deps) {
const currentIndex = hookIndex;
if (hookStates[hookIndex]) {
let [destroy, lastDeps] = hookStates[hookIndex];
let same = deps && deps.every((item, index) => item === lastDeps[index]);
if (same) {
hookIndex++;
} else {
destroy && destroy();
setTimeout(() => {
const destroy = callback();
hookStates[currentIndex] = [destroy, deps];
});
hookIndex++;
}
} else {
//开启一个新的宏任务
setTimeout(() => {
const destroy = callback();
hookStates[currentIndex] = [destroy, deps];
});
hookIndex++;
}
}