class
的情况下使用 state
以及其他的 React 特性import React from './react';
import ReactDOM from './react-dom';
function App(){
const[number,setNumber]=React.useState(0);
let handleClick = ()=> setNumber(number+1)
return (
<div>
<p>{number}</p>
<button onClick={handleClick}>+</button>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
src\react-dom.js
+let hookStates = [];
+let hookIndex = 0;
+let scheduleUpdate;
+function render(vdom, container) {
+ mount(vdom,container);
+ scheduleUpdate = ()=>{
+ hookIndex = 0;
+ compareTwoVdom(container,vdom,vdom);
+ }
+}
+export function useState(initialState){
+ hookStates[hookIndex] = hookStates[hookIndex]||initialState;
+ let currentIndex = hookIndex;
+ function setState(newState){
+ let newState = typeof action === 'function' ? action(oldState) : action;
+ hookStates[currentIndex] = newState;
+ scheduleUpdate();
+ }
+ return [hookStates[hookIndex++],setState];
+}
src\react.js
+import * as hooks from './react-dom';
const React = {
createElement,
Component,
PureComponent,
createRef,
createContext,
cloneElement,
memo,
+ ...hooks
};
export default React;
useCallback
,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算import React from 'react';
import ReactDOM from 'react-dom';
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>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
src\react-dom.js
let hookStates = [];
let hookIndex = 0;
let scheduleUpdate;
function render(vdom,container){
mount(vdom,container);
scheduleUpdate = ()=>{
hookIndex = 0;
compareTwoVdom(container,vdom,vdom);
}
}
export function useState(initialState){
hookStates[hookIndex] = hookStates[hookIndex]||initialState;
let currentIndex = hookIndex;
function setState(newState){
if(typeof newState === 'function') newState=newState(hookStates[currentIndex]);
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;
+ }
+}
const ReactDOM = {
render
};
export default ReactDOM;
src\index.js
import React from './react';
import ReactDOM from './react-dom';
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>
)
}
ReactDOM.render(
<Counter/>,
document.getElementById('root')
);
src\react-dom.js
+export function useReducer(reducer, initialState) {
+ hookStates[hookIndex] = hookStates[hookIndex] || initialState;
+ let currentIndex = hookIndex;
+ function dispatch(action) {
+ //1.获取老状态
+ let oldState = hookStates[currentIndex];
+ //如果有reducer就使用reducer计算新状态
+ if (reducer) {
+ let newState = reducer(oldState, action);
+ hookStates[currentIndex] = newState;
+ } else {
+ //判断action是不是函数,如果是传入老状态,计算新状态
+ let newState = typeof action === 'function' ? action(oldState) : action;
+ hookStates[currentIndex] = newState;
+ }
+ scheduleUpdate();
+ }
+ return [hookStates[hookIndex++], dispatch];
+}
const ReactDOM = {
render
};
export default ReactDOM;
<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';
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>
)
}
ReactDOM.render(<App/>,document.getElementById('root'));
src\react-dom.js
+function useContext(context){
+ return context._currentValue;
+}
componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不过被合并成了一个 APIsrc\index.js
import React from './react';
import ReactDOM from './react-dom';
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>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
src\react-dom.js
+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++;
+ }
+}
const ReactDOM = {
render
};
export default ReactDOM;
useEffect
相同,但它会在所有的 DOM
变更之后同步调用 effectuseEffect
不会阻塞浏览器渲染,而 useLayoutEffect
会浏览器渲染useEffect
会在浏览器渲染结束后执行,useLayoutEffect
则是在 DOM
更新完成后,浏览器绘制之前执行src\index.js
import React from './react';
import ReactDOM from './react-dom';
const Animate = ()=>{
const ref = React.useRef();
React.useLayoutEffect(() => {
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>
)
}
ReactDOM.render(<Animate/>,document.getElementById('root'));
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++];
+}
如何获取最新的state值
import React from 'react';
import ReactDOM from 'react-dom';
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>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
useImperativeHandle
可以让你在使用 ref 时自定义暴露给父组件的实例值import React from './react';
import ReactDOM from './react-dom';
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>
)
}
ReactDOM.render(<Parent/>,document.getElementById('root'));
src\react-dom.js
function mountClassComponent(vdom){
+ const {type, props,ref} = vdom;
const classInstance = new type(props);
+ if(ref){
+ ref.current = classInstance;
+ classInstance.ref = ref;
+ }
vdom.classInstance=classInstance;
if(type.contextType){
classInstance.context = type.contextType.Provider._value;
}
if(classInstance.componentWillMount)
classInstance.componentWillMount();
classInstance.state = getDerivedStateFromProps(classInstance,classInstance.props,classInstance.state)
const renderVdom = classInstance.render();
classInstance.oldRenderVdom=vdom.oldRenderVdom=renderVdom;
const dom = createDOM(renderVdom);
if(classInstance.componentDidMount)
dom.componentDidMount=classInstance.componentDidMount.bind(classInstance);
return dom;
}
+export function useImperativeHandle(ref,handler){
+ ref.current = handler();
+}
const ReactDOM = {
render
};
export default ReactDOM;