class
的情况下使用 state
以及其他的 React 特性import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
function App() {
const [number, setNumber] = React.useState(0);
let handleClick = () => setNumber(number + 1)
return (
<div>
<p>{number}</p>
<button onClick={handleClick}>+</button>
</div>
)
}
let element = <App />
DOMRoot.render(element);
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
+let hookStates = [];
+let hookIndex = 0;
+let scheduleUpdate;
function render(vdom) {
mount(vdom, this.container);
+ scheduleUpdate = () => {
+ hookIndex = 0;
+ compareTwoVdom(this.container, vdom, vdom);
+ }
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
if (newDOM) {
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
}
+export function useState(initialState) {
+ const oldState = hookStates[hookIndex] = hookStates[hookIndex] || initialState;
+ let currentIndex = hookIndex;
+ function setState(action) {
+ let newState = typeof action === 'function' ? action(oldState) : action;
+ hookStates[currentIndex] = newState;
+ scheduleUpdate();
+ }
+ return [hookStates[hookIndex++], setState];
+}
src\react.js
import { wrapToVdom, shallowEqual } from "./utils";
import { REACT_ELEMENT, REACT_FORWARD_REF_TYPE, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "./constants";
import { Component } from './Component';
+import * as hooks from './react-dom/client';
function createElement(type, config, children) {
let ref;
let key;
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;
delete config.ref;
key = config.key;
delete config.key;
}
let props = { ...config };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT,
type,
ref,
key,
props,
};
}
function createRef() {
return { current: null };
}
function forwardRef(render) {
var elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render: render
};
return elementType;
}
function createContext() {
let context = { _currentValue: undefined };
context.Provider = {
$$typeof: REACT_PROVIDER,
_context: context
}
context.Consumer = {
$$typeof: REACT_CONTEXT,
_context: context
}
return context;
}
function cloneElement(element, newProps, children) {
let props = { ...element.props, ...newProps };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom)
} else if (arguments.length === 3) {
props.children = wrapToVdom(children);
}
return {
...element,
props
}
}
class PureComponent extends Component {
shouldComponentUpdate(newProps, nextState) {
return !shallowEqual(this.props, newProps) || !shallowEqual(this.state, nextState);
}
}
function memo(type, compare = shallowEqual) {
return {
$$typeof: REACT_MEMO,
type,
compare
}
}
const React = {
createElement,
Component,
createRef,
forwardRef,
createContext,
cloneElement,
PureComponent,
memo,
+ ...hooks
};
export default React;
useCallback
,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
let Child = ({ data, handleClick }) => {
console.log('Child render');
return (
<button onClick={handleClick}>{data.number}</button>
)
}
Child = React.memo(Child);
function App() {
console.log('App render');
const [name, setName] = React.useState('zhufeng');
const [number, setNumber] = React.useState(0);
let data = React.useMemo(() => ({ number }), [number]);
let handleClick = React.useCallback(() => setNumber(number + 1), [number]);
return (
<div>
<input type="text" value={name} onChange={event => setName(event.target.value)} />
<Child data={data} handleClick={handleClick} />
</div>
)
}
let element = <App />
DOMRoot.render(element);
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
mount(vdom, this.container);
scheduleUpdate = () => {
hookIndex = 0;
compareTwoVdom(this.container, vdom, vdom);
}
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
if (newDOM) {
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
}
export function useState(initialState) {
const oldState = hookStates[hookIndex] = hookStates[hookIndex] || initialState;
let currentIndex = hookIndex;
function setState(action) {
let newState = typeof action === 'function' ? action(oldState) : action;
hookStates[currentIndex] = newState;
scheduleUpdate();
}
return [hookStates[hookIndex++], setState];
}
+export function useMemo(factory, deps) {
+ if (hookStates[hookIndex]) {
+ let [lastMemo, lastDeps] = hookStates[hookIndex];
+ let same = deps.every((item, index) => item === lastDeps[index]);
+ if (same) {
+ hookIndex++;
+ return lastMemo;
+ } else {
+ let newMemo = factory();
+ hookStates[hookIndex++] = [newMemo, deps];
+ return newMemo;
+ }
+ } else {
+ let newMemo = factory();
+ hookStates[hookIndex++] = [newMemo, deps];
+ return newMemo;
+ }
+}
+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;
+ }
+}
src\index.js
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
function reducer(state = { number: 0 }, action) {
switch (action.type) {
case 'ADD':
return { number: state.number + 1 };
case 'MINUS':
return { number: state.number - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = React.useReducer(reducer, { number: 0 });
return (
<div>
Count: {state.number}
<button onClick={() => dispatch({ type: 'ADD' })}>+</button>
<button onClick={() => dispatch({ type: 'MINUS' })}>-</button>
</div>
)
}
let element = <Counter />
DOMRoot.render(element);
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
mount(vdom, this.container);
scheduleUpdate = () => {
hookIndex = 0;
compareTwoVdom(this.container, vdom, vdom);
}
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
if (newDOM) {
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
}
+export function useReducer(reducer, initialState) {
+ hookStates[hookIndex] = hookStates[hookIndex] || initialState;
+ let currentIndex = hookIndex;
+ function dispatch(action) {
+ let oldState = hookStates[currentIndex];
+ if (reducer) {
+ let newState = reducer(oldState, action);
+ hookStates[currentIndex] = newState;
+ } else {
+ let newState = typeof action === 'function' ? action(oldState) : action;
+ hookStates[currentIndex] = newState;
+ }
+ scheduleUpdate();
+ }
+ return [hookStates[hookIndex++], dispatch];
+}
<MyContext.Provider>
的 value prop 决定<MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider
的 context value 值static contextType = MyContext
或者 <MyContext.Consumer>
<MyContext.Provider>
来为下层组件提供 contextsrc\index.js
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
const CounterContext = React.createContext();
function reducer(state, action) {
switch (action.type) {
case 'add':
return { number: state.number + 1 };
case 'minus':
return { number: state.number - 1 };
default:
return state;
}
}
function Counter() {
let { state, dispatch } = React.useContext(CounterContext);
return (
<div>
<p>{state.number}</p>
<button onClick={() => dispatch({ type: 'add' })}>+</button>
<button onClick={() => dispatch({ type: 'minus' })}>-</button>
</div>
)
}
function App() {
const [state, dispatch] = React.useReducer(reducer, { number: 0 });
return (
<CounterContext.Provider value={{ state, dispatch }}>
<Counter />
</CounterContext.Provider>
)
}
let element = <App />
DOMRoot.render(element);
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
mount(vdom, this.container);
scheduleUpdate = () => {
hookIndex = 0;
compareTwoVdom(this.container, vdom, vdom);
}
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
if (newDOM) {
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
}
+export function useContext(context) {
+ return context._currentValue;
+}
componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不过被合并成了一个 APIsrc\index.js
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
function Counter() {
const [number, setNumber] = React.useState(0);
React.useEffect(() => {
console.log('开启一个新的定时器')
const $timer = setInterval(() => {
setNumber(number => number + 1);
}, 1000);
return () => {
console.log('销毁老的定时器');
clearInterval($timer);
}
});
return (
<p>{number}</p>
)
}
let element = <Counter />
DOMRoot.render(element);
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
mount(vdom, this.container);
scheduleUpdate = () => {
hookIndex = 0;
compareTwoVdom(this.container, vdom, vdom);
}
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
if (newDOM) {
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
}
+export function useEffect(callback, dependencies) {
+ let currentIndex = hookIndex;
+ if (hookStates[hookIndex]) {
+ let [destroy, lastDeps] = hookStates[hookIndex];
+ let same = dependencies && dependencies.every((item, index) => item === lastDeps[index]);
+ if (same) {
+ hookIndex++;
+ } else {
+ destroy && destroy();
+ setTimeout(() => {
+ hookStates[currentIndex] = [callback(), dependencies];
+ });
+ hookIndex++;
+ }
+ } else {
+ setTimeout(() => {
+ hookStates[currentIndex] = [callback(), dependencies];
+ });
+ hookIndex++;
+ }
+}
useEffect
相同,但它会在所有的 DOM
变更之后同步调用 effectuseEffect
不会阻塞浏览器渲染,而 useLayoutEffect
会浏览器渲染useEffect
会在浏览器渲染结束后执行,useLayoutEffect
则是在 DOM
更新完成后,浏览器绘制之前执行src\index.js
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
const Animate = () => {
const ref = React.useRef();
React.useEffect(() => {
ref.current.style.transform = `translate(500px)`;//TODO
ref.current.style.transition = `all 500ms`;
});
let style = {
width: '100px',
height: '100px',
borderRadius: '50%',
backgroundColor: 'red'
}
return (
<div style={style} ref={ref}></div>
)
}
let element = <Animate />
DOMRoot.render(element);
src\react-dom.js
+export function useLayoutEffect(callback, dependencies) {
+ let currentIndex = hookIndex;
+ if (hookStates[hookIndex]) {
+ let [destroy, lastDeps] = hookStates[hookIndex];
+ let same = dependencies && dependencies.every((item, index) => item === lastDeps[index]);
+ if (same) {
+ hookIndex++;
+ } else {
+ destroy && destroy();
+ queueMicrotask(() => {
+ hookStates[currentIndex] = [callback(), dependencies];
+ });
+ hookIndex++
+ }
+ } else {
+ queueMicrotask(() => {
+ hookStates[currentIndex] = [callback(), dependencies];
+ });
+ hookIndex++;
+ }
+}
+export function useRef(initialState) {
+ hookStates[hookIndex] = hookStates[hookIndex] || { current: initialState };
+ return hookStates[hookIndex++];
+}
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
function Counter() {
let valueRef = React.useRef();
const [state, setState] = React.useState(0)
const handleClick = () => {
let newValue = state + 1;
valueRef.current = newValue;
setState(newValue)
otherFun();
}
function otherFun() {
console.log('state', valueRef.current);
}
return (
<div>
<p>state:{state}</p>
<button onClick={handleClick}>+</button>
</div>
)
}
let element = <Counter />
DOMRoot.render(element);
useImperativeHandle
可以让你在使用 ref 时自定义暴露给父组件的实例值import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
function Child(props, ref) {
const inputRef = React.useRef();
React.useImperativeHandle(ref, () => (
{
focus() {
inputRef.current.focus();
}
}
));
return (
<input type="text" ref={inputRef} />
)
}
const ForwardChild = React.forwardRef(Child);
function Parent() {
let [number, setNumber] = React.useState(0);
const inputRef = React.useRef();
function getFocus() {
console.log(inputRef.current);
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<div>
<ForwardChild ref={inputRef} />
<button onClick={getFocus}>获得焦点</button>
<p>{number}</p>
<button onClick={() => {
debugger
setNumber(number + 1)
}}>+</button>
</div>
)
}
let element = <Parent />
DOMRoot.render(element);
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom) {
mount(vdom, this.container);
scheduleUpdate = () => {
hookIndex = 0;
compareTwoVdom(this.container, vdom, vdom);
}
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
if (newDOM) {
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
}
+export function useImperativeHandle(ref, handler) {
+ ref.current = handler();
+}