thisconst [state, setState] = useState(initialState);
import React,{useState} from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
render() {
return (
<div>
<p>{this.state.number}</p>
<button onClick={() => this.setState({ number: this.state.number + 1 })}>
+
</button>
</div>
);
}
}
function Counter2(){
const [number,setNumber] = useState(0);
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
</>
)
}
export default Counter2;
function Counter2(){
const [number,setNumber] = useState(0);
function alertNumber(){
setTimeout(()=>{
alert(number);
},3000);
}
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
function Counter() {
const [number, setNumber] = useState(0);
const savedCallback = useRef();
function alertNumber() {
setTimeout(() => {
alert(savedCallback.current);
}, 3000);
}
return (
<>
<p>{number}</p>
<button onClick={() => {
setNumber(number + 1);
savedCallback.current = number + 1;
}}>+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
function Counter2(){
const [number,setNumber] = useState(0);
let numberRef = useRef(number);
numberRef.current = number;
function alertNumber(){
setTimeout(()=>{
alert(numberRef.current);
},3000);
}
+ function lazy(){
+ setTimeout(()=>{
+ setNumber(number+1);
+ },3000);
+ }
+ function lazyFunc(){
+ setTimeout(()=>{
+ setNumber(number=>number+1);
+ },3000);
+ }
return (
<>
<p>{number}</p>
<button onClick={()=>setNumber(number+1)}>+</button>
<button onClick={lazy}>lazy+</button>
<button onClick={lazyFunc}>lazyFunc+</button>
<button onClick={alertNumber}>alertNumber</button>
</>
)
}
function Counter(){
const [{name,number},setValue] = useState(()=>{
return {name:'计数器',number:0};
});
return (
<>
<p>{name}:{number}</p>
<button onClick={()=>setValue({number:number+1})}>+</button>
</>
)
}
function Counter4(){
const [counter,setCounter] = useState({name:'计数器',number:0});
console.log('render Counter')
return (
<>
<p>{counter.name}:{counter.number}</p>
<button onClick={()=>setCounter({...counter,number:counter.number+1})}>+</button>
<button onClick={()=>setCounter(counter)}>-</button>
</>
)
}
useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新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('zhufeng');
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>
)
}
import React from 'react';
import ReactDOM from 'react-dom';
let hookStates = [];
let hookIndex = 0;
function useState(initialState){
hookStates[hookIndex] = hookStates[hookIndex]||initialState;
let currentIndex = hookIndex;
function setState(newState){
hookStates[currentIndex]=newState;
render();
}
return [hookStates[hookIndex++],setState];
}
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;
}
}
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;
}
}
let Child = ({data,handleClick})=>{
console.log('Child render');
return (
<button onClick={handleClick}>{data.number}</button>
)
}
class PureComponent extends React.Component {
shouldComponentUpdate(newProps,nextState) {
return !shallowEqual(this.props, newProps)||!shallowEqual(this.state, nextState);
}
}
function shallowEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (typeof obj1 != "object" ||obj1 === null ||typeof obj2 != "object" ||obj2 === null) {
return false;
}
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}
function memo(OldComponent){
return class extends PureComponent{
render(){
return <OldComponent {...this.props}/>
}
}
}
Child = memo(Child);
function App(){
console.log('App render');
const[name,setName]=useState('zhufeng');
const[number,setNumber]=useState(0);
let data = useMemo(()=>({number}),[number]);
let handleClick = useCallback(()=> setNumber(number+1),[number]);
return (
<div>
<input type="text" value={name} onChange={event=>setName(event.target.value)}/>
<Child data={data} handleClick={handleClick}/>
</div>
)
}
function render(){
hookIndex=0;
ReactDOM.render(
<App />,
document.getElementById('root')
);
}
render();
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function App() {
const [number, setNumber] = useState(0);
const [visible, setVisible] = useState(false);
if (number % 2 == 0) {
useEffect(() => {
setVisible(true);
}, [number]);
} else {
useEffect(() => {
setVisible(false);
}, [number]);
}
return (
<div>
<p>{number}</p>
<p>{visible && <div>visible</div>}</p>
<button onClick={() => setNumber(number + 1)}>+</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
const [state, dispatch] = useReducer(reducer, initialArg, init);
import React from 'react';
import ReactDOM from 'react-dom';
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 init(initialState){
return {number:initialState};
}
function Counter1(){
const [state, setState] = React.useState({number:0});
return (
<>
Count: {state.number}
<button onClick={() => setState({number:state.number+1})}>+</button>
<button onClick={() => setState({number:state.number-1})}>-</button>
</>
)
}
function Counter2(){
const [state, dispatch] = React.useReducer(reducer, 0,init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({type: 'add'})}>+</button>
<button onClick={() => dispatch({type: 'minus'})}>-</button>
</>
)
}
function render(){
ReactDOM.render(
<>
<Counter1 />
<Counter2 />
</>,
document.getElementById('root')
);
}
render();
实现
import React from 'react';
import ReactDOM from 'react-dom';
let hookStates = [];
let hookIndex = 0;
function useReducer(reducer, initialState,init) {
hookStates[hookIndex]=hookStates[hookIndex]||(init?init(initialState):initialState);
let currentIndex = hookIndex;
function dispatch(action) {
hookStates[currentIndex]=reducer?reducer(hookStates[currentIndex],action):action;
render();
}
return [hookStates[hookIndex++], dispatch];
}
function useState(initialState){
return useReducer(null,initialState);
}
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 init(initialState){
return {number:initialState};
}
function Counter1(){
const [state, setState] = useState({number:0});
return (
<>
Count: {state.number}
<button onClick={() => setState({number:state.number+1})}>+</button>
<button onClick={() => setState({number:state.number-1})}>-</button>
</>
)
}
function Counter2(){
const [state, dispatch] = useReducer(reducer, 0,init);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({type: 'add'})}>+</button>
<button onClick={() => dispatch({type: 'minus'})}>-</button>
</>
)
}
function render(){
hookIndex=0;
ReactDOM.render(
<>
<Counter1 />
<Counter2 />
</>,
document.getElementById('root')
);
}
render();
/*
useMemo<{
number: number;
}>(factory: () => {
number: number;
}, deps: React.DependencyList): {
number: number;
} */
static contextType = MyContext 或者 <MyContext.Consumer>
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 (
<>
<p>{state.number}</p>
<button onClick={() => dispatch({type: 'add'})}>+</button>
<button onClick={() => dispatch({type: 'minus'})}>-</button>
</>
)
}
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'));
function useContext(context){
return context._currentValue;
}
componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 APIuseEffect(didUpdate);
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 属于一次特定的渲染。
function Counter(){
const [number,setNumber] = useState(0);
// 相当于componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log('开启一个新的定时器')
const $timer = setInterval(()=>{
setNumber(number=>number+1);
},1000);
},[]);
return (
<>
<p>{number}</p>
</>
)
}
import React, { useEffect, useState, useReducer } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [number, setNumber] = useState(0);
useEffect(() => {
console.log('开启一个新的定时器')
const $timer = setInterval(() => {
setNumber(number => number + 1);
}, 1000);
return () => {
console.log('销毁老的定时器');
clearInterval($timer);
}
});
return (
<>
<p>{number}</p>
</>
)
}
function App() {
let [visible, setVisible] = useState(true);
return (
<div>
{visible && <Counter />}
<button onClick={() => setVisible(false)}>stop</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
import React,{useEffect,useState} from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log('setInterval',count);
setCount(count + 1);
}, 1000);
return () => {
console.log('clearInterval');
clearInterval(id);
};
}, [count]);
return <h1>{count}</h1>;
}
干掉对count的依赖
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count=>count + 1);
}, 1000);
return () => {
clearInterval(id);
};
}, []);
return <h1>{count}</h1>;
}
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
const API = {
async fetchArticle(id){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve({id,title:`title_${id}`});
},1000*(5-id));
});
}
}
function Article({ id }) {
const [article, setArticle] = useState({});
useEffect(() => {
let didCancel = false;
async function fetchData() {
const article = await API.fetchArticle(id);
if (!didCancel) {
setArticle(article);
}
}
fetchData();
return () => {
didCancel = true;
};
}, [id]);
return (
<div>
<p>{article.title}</p>
</div>
)
}
function App(){
let [id,setId] = useState(1);
return (
<div>
<p>id:{id}</p>
<Article id={id}/>
<button onClick={()=>setId(id+1)}>改变id</button>
</div>
)
}
function render() {
ReactDOM.render(
<App />,
document.getElementById('root')
);
}
render();
index.js
import React,{useEffect,useRef,useState} from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect(() => {
latestCount.current = count;
setTimeout(() => {
console.log(`You clicked ${latestCount.current} times`);
}, 3000);
});
return (
<div>
<p>{count}</p>
<button onClick={()=>setCount(count+1)}>+</button>
</div>
)
}
function render(){
ReactDOM.render(
<Counter/>,
document.getElementById('root')
);
}
render();
+let hookStates = [];
+let hookIndex = 0;
+function useState(initialState){
+ hookStates[hookIndex]=hookStates[hookIndex]||initialState;
+ let currentIndex = hookIndex;
+ function setState(newState){
+ hookStates[currentIndex]=newState;
+ render();
+ }
+ return [hookStates[hookIndex++],setState];
+}
+function useEffect(callback,dependencies){
+ if(hookStates[hookIndex]){
+ let lastDeps = hookStates[hookIndex];
+ let same = dependencies.every((item,index)=>item === lastDeps[index]);
+ if(same){
+ hookIndex++;
+ }else{
+ hookStates[hookIndex++]=dependencies;
+ setTimeout(callback);
+ }
+ }else{
+ hookStates[hookIndex++]=dependencies;
+ setTimeout(callback);
+ }
+}
.current 属性被初始化为传入的参数(initialValue)const refContainer = useRef(initialValue);
import React, { useState, useRef } from 'react';
import ReactDOM from 'react-dom';
function Parent() {
let [number, setNumber] = useState(0);
return (
<>
<Child />
<button onClick={() => setNumber({ number: number + 1 })}>+</button>
</>
)
}
let input;
function Child() {
const inputRef = useRef();
console.log('input===inputRef', input === inputRef);
input = inputRef;
function getFocus() {
inputRef.current.focus();
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={getFocus}>获得焦点</button>
</>
)
}
ReactDOM.render(<Parent />, document.getElementById('root'));
function Child(props,ref){
return (
<input type="text" ref={ref}/>
)
}
Child = forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>获得焦点</button>
</>
)
}
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值function Child(props,ref){
const inputRef = useRef();
useImperativeHandle(ref,()=>(
{
focus(){
inputRef.current.focus();
}
}
));
return (
<input type="text" ref={inputRef}/>
)
}
Child = forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
const inputRef = useRef();
function getFocus(){
console.log(inputRef.current);
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<Child ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>获得焦点</button>
</>
)
}
import React from 'react';
import ReactDOM from 'react-dom';
let hookStates = [];
let hookIndex = 0;
function useState(initialState) {
hookStates[hookIndex] = hookStates[hookIndex] || initialState;
let currentIndex = hookIndex;
function setState(newState) {
hookStates[currentIndex] = newState;
render();
}
return [hookStates[hookIndex++], setState];
}
function useRef(initialState) {
hookStates[hookIndex] = hookStates[hookIndex] || { current: initialState };
return hookStates[hookIndex++];
}
let lastInput;
function App() {
let [number, setNumber] = useState(0);
const inputRef = useRef();
console.log('input===inputRef', lastInput === inputRef);
lastInput = inputRef;
return (
<>
<input type="text" ref={inputRef} />
<button onClick={()=>setNumber(number+1)}>{number}</button>
</>
)
}
function render() {
hookIndex = 0;
ReactDOM.render(<App />, document.getElementById('root'));
}
render();
useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effectuseEffect不会阻塞浏览器渲染,而 useLayoutEffect 会浏览器渲染useEffect会在浏览器渲染结束后执行,useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制之前执行resize事件scroll方法requestAnimationFrame 的回调requestIdleCallback的回调函数
import React, {useRef } from 'react';
import ReactDOM from 'react-dom';
let lastDependencies;
function useEffect(callback,dependencies){
if(lastDependencies){
let changed = !dependencies.every((item,index)=>item==lastDependencies[index]);
if(changed){
setTimeout(callback)
lastDependencies=dependencies;
}
}else{
setTimeout(callback)
lastDependencies=dependencies;
}
}
let lastLayoutDependencies;
function useLayoutEffect(callback,dependencies){
if(lastLayoutDependencies){
let changed = !dependencies.every((item,index)=>item==lastLayoutDependencies[index]);
if(changed){
queueMicrotask(callback);
lastLayoutDependencies=dependencies;
}
}else{
Promise.resolve().then(callback);
lastLayoutDependencies=dependencies;
}
}
const Animate = ()=>{
const ref = useRef();
useLayoutEffect(()=>{
ref.current.style.WebkitTransform = `translate(500px)`;
ref.current.style.transition = `all 500ms`;
});
let style = {
width:'100px',
height:'100px',
backgroundColor:'red'
}
return (
<div>
<div style={style} ref={ref}></div>
</div>
)
}
function render(){
ReactDOM.render(<Animate/>,document.getElementById('root'));
}
render();
function useLayoutEffect(callback,dependencies){
if(hookStates[hookIndex]){
let lastDeps = hookStates[hookIndex];
let same = dependencies.every((item,index)=>item === lastDeps[index]);
if(same){
hookIndex++;
}else{
hookStates[hookIndex++]=dependencies;
+ queueMicrotask(callback);
}
}else{
hookStates[hookIndex++]=dependencies;
queueMicrotask(callback);
}
}
function useNumber(){
const [number,setNumber] = useState(0);
useEffect(() => {
console.log('开启一个新的定时器')
const $timer = setInterval(()=>{
setNumber(number+1);
},1000);
return ()=>{
console.log('销毁老的定时器')
clearInterval($timer);
}
});
return number;
}
function Counter1(){
let number1 = useNumber();
return (
<>
<p>{number1}</p>
</>
)
}
function Counter2(){
let number = useNumber();
return (
<>
<p>{number}</p>
</>
)
}
function App(){
return <><Counter1/><Counter2/></>
}
import React, { useState, useEffect, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
function useRequest(url) {
let limit = 5;
let [offset, setOffset] = useState(0);
let [data, setData] = useState([]);
function loadMore() {
setData(null);
fetch(`${url}?offset=${offset}&limit=${limit}`)
.then(response => response.json())
.then(pageData => {
setData([...data, ...pageData]);
setOffset(offset + pageData.length);
});
}
useEffect(loadMore, []);
return [data, loadMore];
}
function App() {
const [users, loadMore] = useRequest('http://localhost:8000/api/users');
if (users === null) {
return <div>正在加载中....</div>
}
return (
<>
<ul>
{
users.map((item, index) => <li key={index}>{item.id}:{item.name}</li>)
}
</ul>
<button onClick={loadMore}>加载更多</button>
</>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
async+await
import React, { useState, useEffect, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
function useRequest(url) {
let limit = 5;
let [offset, setOffset] = useState(0);
let [data, setData] = useState([]);
async function loadMore() {
setData(null);
let pageData = await fetch(`${url}?offset=${offset}&limit=${limit}`)
.then(response => response.json());
setData([...data, ...pageData]);
setOffset(offset + pageData.length);
}
useEffect(loadMore, []);
return [data, loadMore];
}
function App() {
const [users, loadMore] = useRequest('http://localhost:8000/api/users');
if (users === null) {
return <div>正在加载中....</div>
}
return (
<>
<ul>
{
users.map((item, index) => <li key={index}>{item.id}:{item.name}</li>)
}
</ul>
<button onClick={loadMore}>加载更多</button>
</>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
let express = require('express');
let app = express();
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
next();
});
app.get('/api/users', function (req, res) {
let offset = parseInt(req.query.offset);
let limit = parseInt(req.query.limit);
let result = [];
for (let i = offset; i < offset + limit; i++) {
result.push({ id: i + 1, name: 'name' + (i + 1) });
}
res.json(result);
});
app.listen(8000);
import React, { useState, useEffect, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
function useMove(initialClassName) {
const [className, setClassName] = useState(initialClassName);
const [state, setState] = useState('');
function start() {
setState('bigger');
}
useEffect(() => {
if (state === 'bigger') {
setClassName(`${initialClassName} ${initialClassName}-bigger`);
}
}, [state]);
return [className, start];
}
function App() {
const [className, start] = useMove('circle');
return (
<div>
<button onClick={start}>start</button>
<div className={className}></div>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
.circle {
width : 50px;
height : 50px;
border-radius: 50%;
background : red;
transition: all .5s;
}
.circle-bigger {
width : 200px;
height : 200px;
}