componentDidMount
中注册事件以及其余的逻辑,在 componentWillUnmount
中卸载事件,这样分散不集中的写法,很容易写出 bug )class App extends React.Component<any, any> {
handleClick2;
constructor(props) {
super(props);
this.state = {
num: 1,
title: ' react study'
};
this.handleClick2 = this.handleClick1.bind(this);
}
handleClick1() {
this.setState({
num: this.state.num + 1,
})
}
handleClick3 = () => {
this.setState({
num: this.state.num + 1,
})
};
render() {
return (<div>
<h2>Ann, {this.state.num}</h2>
<button onClick={this.handleClick2}>btn1</button>
<button onClick={this.handleClick1.bind(this)}>btn2</button>
<button onClick={() => this.handleClick1()}>btn3</button>
<button onClick={this.handleClick3}>btn4</button>
</div>)
}
}
复制代码
前提:子组件内部作了性能优化,如(React.PureComponent)javascript
综上所述,若是不注意的话,很容易写成第三种写法,致使性能上有所损耗。html
ajax
请求、访问原生dom
元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。以往这些反作用都是写在类组件生命周期函数中的。而 useEffect
在所有渲染完毕后才会执行,useLayoutEffect
会在浏览器 layout
以后,painting
以前执行。// 这里能够任意命名,由于返回的是数组,数组解构
const [state, setState] = useState(initialState);
复制代码
import React, { useState } from "react";
import ReactDOM from "react-dom";
function Child1(porps) {
console.log(porps);
const { num, handleClick } = porps;
return (
<div onClick={() => { handleClick(num + 1); }} > child </div>
);
}
function Child2(porps) {
// console.log(porps);
const { text, handleClick } = porps;
return (
<div>
child2
<Grandson text={text} handleClick={handleClick} />
</div>
);
}
function Grandson(porps) {
console.log(porps);
const { text, handleClick } = porps;
return (
<div
onClick={() => {
handleClick(text + 1);
}}
>
grandson
</div>
);
}
function Parent() {
let [num, setNum] = useState(0);
let [text, setText] = useState(1);
return (
<div>
<Child1 num={num} handleClick={setNum} />
<Child2 text={text} handleClick={setText} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
复制代码
function Counter2(){
let [number,setNumber] = useState(0);
function alertNumber(){
setTimeout(()=>{
// alert 只能获取到点击按钮时的那个状态
alert(number);
},3000);
}
return (
<> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> <button onClick={alertNumber}>alertNumber</button> </> ) } 复制代码
function Counter(){
let [number,setNumber] = useState(0);
function lazy(){
setTimeout(() => {
// setNumber(number+1);
// 这样每次执行时都会去获取一遍 state,而不是使用点击触发时的那个 state
setNumber(number=>number+1);
}, 3000);
}
return (
<> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> <button onClick={lazy}>lazy</button> </> ) } 复制代码
function Counter5(props){
console.log('Counter5 render');
// 这个函数只在初始渲染时执行一次,后续更新状态从新渲染组件时,该函数就不会再被调用
function getInitState(){
return {number:props.number};
}
let [counter,setCounter] = useState(getInitState);
return (
<> <p>{counter.number}</p> <button onClick={()=>setCounter({number:counter.number+1})}>+</button> <button onClick={()=>setCounter(counter)}>setCounter</button> </> ) } 复制代码
function Counter(){
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> </> ) } 复制代码
pureComponent
;React.memo
,将函数组件传递给 memo
以后,就会返回一个新的组件,新组件的功能:若是接受到的属性不变,则不从新渲染函数;const [number,setNumber] = useState(0)
也就是说每次都会生成一个新的值(哪怕这个值没有变化),即便使用了 React.memo
,也仍是会从新渲染import React,{useState,memo,useMemo,useCallback} from 'react';
function SubCounter({onClick,data}){
console.log('SubCounter render');
return (
<button onClick={onClick}>{data.number}</button>
)
}
SubCounter = memo(SubCounter);
export default function Counter6(){
console.log('Counter render');
const [name,setName]= useState('计数器');
const [number,setNumber] = useState(0);
const data ={number};
const addClick = ()=>{
setNumber(number+1);
};
return (
<>
<input type="text" value={name} onChange={(e)=>setName(e.target.value)}/>
<SubCounter data={data} onClick={addClick}/>
</>
)
}
复制代码
useMemo
,它仅会在某个依赖项改变时才从新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算import React,{useState,memo,useMemo,useCallback} from 'react';
function SubCounter({onClick,data}){
console.log('SubCounter render');
return (
<button onClick={onClick}>{data.number}</button>
)
}
SubCounter = memo(SubCounter);
let oldData,oldAddClick;
export default function Counter2(){
console.log('Counter render');
const [name,setName]= useState('计数器');
const [number,setNumber] = useState(0);
// 父组件更新时,这里的变量和函数每次都会从新建立,那么子组件接受到的属性每次都会认为是新的
// 因此子组件也会随之更新,这时候能够用到 useMemo
// 有没有后面的依赖项数组很重要,不然仍是会从新渲染
// 若是后面的依赖项数组没有值的话,即便父组件的 number 值改变了,子组件也不会去更新
//const data = useMemo(()=>({number}),[]);
const data = useMemo(()=>({number}),[number]);
console.log('data===oldData ',data===oldData);
oldData = data;
// 有没有后面的依赖项数组很重要,不然仍是会从新渲染
const addClick = useCallback(()=>{
setNumber(number+1);
},[number]);
console.log('addClick===oldAddClick ',addClick===oldAddClick);
oldAddClick=addClick;
return (
<>
<input type="text" value={name} onChange={(e)=>setName(e.target.value)}/>
<SubCounter data={data} onClick={addClick}/>
</>
)
}
复制代码
import React from 'react';
import ReactDOM from 'react-dom';
let firstWorkInProgressHook = {memoizedState: null, next: null};
let workInProgressHook;
function useState(initState) {
let currentHook = workInProgressHook.next ? workInProgressHook.next : {memoizedState: initState, next: null};
function setState(newState) {
currentHook.memoizedState = newState;
render();
}
// 这就是为何 useState 书写顺序很重要的缘由
// 假如某个 useState 没有执行,会致使指针移动出错,数据存取出错
if (workInProgressHook.next) {
// 这里只有组件刷新的时候,才会进入
// 根据书写顺序来取对应的值
// console.log(workInProgressHook);
workInProgressHook = workInProgressHook.next;
} else {
// 只有在组件初始化加载时,才会进入
// 根据书写顺序,存储对应的数据
// 将 firstWorkInProgressHook 变成一个链表结构
workInProgressHook.next = currentHook;
// 将 workInProgressHook 指向 {memoizedState: initState, next: null}
workInProgressHook = currentHook;
// console.log(firstWorkInProgressHook);
}
return [currentHook.memoizedState, setState];
}
function Counter() {
// 每次组件从新渲染的时候,这里的 useState 都会从新执行
const [name, setName] = useState('计数器');
const [number, setNumber] = useState(0);
return (
<> <p>{name}:{number}</p> <button onClick={() => setName('新计数器' + Date.now())}>新计数器</button> <button onClick={() => setNumber(number + 1)}>+</button> </> ) } function render() { // 每次从新渲染的时候,都将 workInProgressHook 指向 firstWorkInProgressHook workInProgressHook = firstWorkInProgressHook; ReactDOM.render(<Counter/>, document.getElementById('root')); } render(); 复制代码
let initialState = 0;
// 若是你但愿初始状态是一个{number:0}
// 能够在第三个参数中传递一个这样的函数 ()=>({number:initialState})
// 这个函数是一个惰性初始化函数,能够用来进行复杂的计算,而后返回最终的 initialState
const [state, dispatch] = useReducer(reducer, initialState, init);
复制代码
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 init(initialState){
return {number:initialState};
}
function Counter(){
const [state, dispatch] = useReducer(reducer, initialState,init);
return (
<> Count: {state.number} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ) } 复制代码
static contextType = MyContext
或者 <MyContext.Consumer>
import React,{useState,memo,useMemo,useCallback,useReducer,createContext,useContext} from 'react';
import ReactDOM from 'react-dom';
const initialState = 0;
function reducer(state=initialState,action){
switch(action.type){
case 'ADD':
return {number:state.number+1};
default:
break;
}
}
const CounterContext = createContext();
// 第一种获取 CounterContext 方法:不使用 hook
function SubCounter_one(){
return (
<CounterContext.Consumer>
{
value=>(
<>
<p>{value.state.number}</p>
<button onClick={()=>value.dispatch({type:'ADD'})}>+</button>
</>
)
}
</CounterContext.Consumer>
)
}
// 第二种获取 CounterContext 方法:使用 hook ,更简洁
function SubCounter(){
const {state, dispatch} = useContext(CounterContext);
return (
<>
<p>{state.number}</p>
<button onClick={()=>dispatch({type:'ADD'})}>+</button>
</>
)
}
/* class SubCounter extends React.Component{
static contextTypes = CounterContext
this.context = {state, dispatch}
} */
function Counter(){
const [state, dispatch] = useReducer((reducer), initialState, ()=>({number:initialState}));
return (
<CounterContext.Provider value={{state, dispatch}}>
<SubCounter/>
</CounterContext.Provider>
)
}
ReactDOM.render(<Counter />, document.getElementById('root'));
复制代码
ajax
请求、访问原生dom
元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。componentDidMount
、componentDidUpdate
和 componentWillUnmount
具备相同的用途,只不过被合并成了一个 APIcomponentDidMount
或 componentDidUpdate
不一样,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数状况下,effect 不须要同步地执行。在个别状况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。class Counter extends React.Component{
state = {number:0};
add = ()=>{
this.setState({number:this.state.number+1});
};
componentDidMount(){
this.changeTitle();
}
componentDidUpdate(){
this.changeTitle();
}
changeTitle = ()=>{
document.title = `你已经点击了${this.state.number}次`;
};
render(){
return (
<> <p>{this.state.number}</p> <button onClick={this.add}>+</button> </> ) } } 复制代码
import React,{Component,useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
function Counter(){
const [number,setNumber] = useState(0);
// useEffect里面的这个函数会在第一次渲染以后和更新完成后执行
// 至关于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
document.title = `你点击了${number}次`;
});
return (
<> <p>{number}</p> <button onClick={()=>setNumber(number+1)}>+</button> </> ) } ReactDOM.render(<Counter />, document.getElementById('root')); 复制代码
function Counter(){
let [number,setNumber] = useState(0);
let [text,setText] = useState('');
// 至关于componentDidMount 和 componentDidUpdate
useEffect(()=>{
console.log('开启一个新的定时器')
let $timer = setInterval(()=>{
setNumber(number=>number+1);
},1000);
// useEffect 若是返回一个函数的话,该函数会在组件卸载和更新时调用
// useEffect 在执行反作用函数以前,会先调用上一次返回的函数
// 若是要清除反作用,要么返回一个清除反作用的函数
/* return ()=>{ console.log('destroy effect'); clearInterval($timer); } */
});
// },[]);//要么在这里传入一个空的依赖项数组,这样就不会去重复执行
return (
<>
<input value={text} onChange={(event)=>setText(event.target.value)}/>
<p>{number}</p>
<button>+</button>
</>
)
}
复制代码
function Counter(){
let [number,setNumber] = useState(0);
let [text,setText] = useState('');
// 至关于componentDidMount 和 componentDidUpdate
useEffect(()=>{
console.log('useEffect');
let $timer = setInterval(()=>{
setNumber(number=>number+1);
},1000);
},[text]);// 数组表示 effect 依赖的变量,只有当这个变量发生改变以后才会从新执行 efffect 函数
return (
<>
<input value={text} onChange={(event)=>setText(event.target.value)}/>
<p>{number}</p>
<button>+</button>
</>
)
}
复制代码
// 类组件版
class FriendStatusWithCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0, isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
// ...
复制代码
能够发现设置 document.title
的逻辑是如何被分割到 componentDidMount
和 componentDidUpdate
中的,订阅逻辑又是如何被分割到 componentDidMount
和 componentWillUnmount
中的。并且 componentDidMount
中同时包含了两个不一样功能的代码。这样会使得生命周期函数很混乱。java
Hook 容许咱们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的 每个 effect。react
// Hooks 版
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
复制代码
function LayoutEffect() {
const [color, setColor] = useState('red');
useLayoutEffect(() => {
alert(color);
});
useEffect(() => {
console.log('color', color);
});
return (
<> <div id="myDiv" style={{ background: color }}>颜色</div> <button onClick={() => setColor('red')}>红</button> <button onClick={() => setColor('yellow')}>黄</button> <button onClick={() => setColor('blue')}>蓝</button> </> ); } 复制代码
current
属性被初始化为传入的参数(initialValue)const refContainer = useRef(initialValue);
复制代码
import React, { useState, useEffect, 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 Parent() {
return (
<>
// <Child ref={xxx} /> 这样是不行的
<Child />
<button>+</button>
</>
)
}
复制代码
function Child(props,ref){
return (
<input type="text" ref={ref}/>
)
}
Child = React.forwardRef(Child);
function Parent(){
let [number,setNumber] = useState(0);
// 在使用类组件的时候,建立 ref 返回一个对象,该对象的 current 属性值为空
// 只有当它被赋给某个元素的 ref 属性时,才会有值
// 因此父组件(类组件)建立一个 ref 对象,而后传递给子组件(类组件),子组件内部有元素使用了
// 那么父组件就能够操做子组件中的某个元素
// 可是函数组件没法接收 ref 属性 <Child ref={xxx} /> 这样是不行的
// 因此就须要用到 forwardRef 进行转发
const inputRef = useRef();//{current:''}
function getFocus(){
inputRef.current.value = 'focus';
inputRef.current.focus();
}
return (
<>
<ForwardChild ref={inputRef}/>
<button onClick={()=>setNumber({number:number+1})}>+</button>
<button onClick={getFocus}>得到焦点</button>
</>
)
}
复制代码
useImperativeHandle
可让你在使用 ref 时,自定义暴露给父组件的实例值,不能让父组件想干吗就干吗import React,{useState,useEffect,createRef,useRef,forwardRef,useImperativeHandle} from 'react';
function Child(props,parentRef){
// 子组件内部本身建立 ref
let focusRef = useRef();
let inputRef = useRef();
useImperativeHandle(parentRef,()=>(
// 这个函数会返回一个对象
// 该对象会做为父组件 current 属性的值
// 经过这种方式,父组件可使用操做子组件中的多个 ref
return {
focusRef,
inputRef,
name:'计数器',
focus(){
focusRef.current.focus();
},
changeText(text){
inputRef.current.value = text;
}
}
});
return (
<>
<input ref={focusRef}/>
<input ref={inputRef}/>
</>
)
}
Child = forwardRef(Child);
function Parent(){
const parentRef = useRef();//{current:''}
function getFocus(){
parentRef.current.focus();
// 由于子组件中没有定义这个属性,实现了保护,因此这里的代码无效
parentRef.current.addNumber(666);
parentRef.current.changeText('<script>alert(1)</script>');
console.log(parentRef.current.name);
}
return (
<>
<ForwardChild ref={parentRef}/>
<button onClick={getFocus}>得到焦点</button>
</>
)
}
复制代码
import React, { useLayoutEffect, useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
function useNumber(){
let [number,setNumber] = useState(0);
useEffect(()=>{
setInterval(()=>{
setNumber(number=>number+1);
},1000);
},[]);
return [number,setNumber];
}
// 每一个组件调用同一个 hook,只是复用 hook 的状态逻辑,并不会共用一个状态
function Counter1(){
let [number,setNumber] = useNumber();
return (
<div><button onClick={()=>{ setNumber(number+1) }}>{number}</button></div>
)
}
function Counter2(){
let [number,setNumber] = useNumber();
return (
<div><button onClick={()=>{ setNumber(number+1) }}>{number}</button></div>
)
}
ReactDOM.render(<><Counter1 /><Counter2 /></>, document.getElementById('root')); 复制代码
{
"plugins": ["react-hooks"],
// ...
"rules": {
"react-hooks/rules-of-hooks": 'error',// 检查 Hook 的规则
"react-hooks/exhaustive-deps": 'warn' // 检查 effect 的依赖
}
}
复制代码
react.docschina.org/docs/hooks-…ios
useState
和 useEffect
调用之间保持 hook 状态的正确性function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
复制代码
// ------------
// 首次渲染
// ------------
useState('Mary') // 1. 使用 'Mary' 初始化变量名为 name 的 state
useEffect(persistForm) // 2. 添加 effect 以保存 form 操做
useState('Poppins') // 3. 使用 'Poppins' 初始化变量名为 surname 的 state
useEffect(updateTitle) // 4. 添加 effect 以更新标题
// -------------
// 二次渲染
// -------------
useState('Mary') // 1. 读取变量名为 name 的 state(参数被忽略)
useEffect(persistForm) // 2. 替换保存 form 的 effect
useState('Poppins') // 3. 读取变量名为 surname 的 state(参数被忽略)
useEffect(updateTitle) // 4. 替换更新标题的 effect
// ...
复制代码
只要 Hook 的调用顺序在屡次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联。但若是咱们将一个 Hook (例如 persistForm
effect) 调用放到一个条件语句中会发生什么呢?git
// 🔴 在条件语句中使用 Hook 违反第一条规则
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
复制代码
在第一次渲染中 name !== ''
这个条件值为 true
,因此咱们会执行这个 Hook。可是下一次渲染时咱们可能清空了表单,表达式值变为 false
。此时的渲染会跳过该 Hook,Hook 的调用顺序发生了改变:github
useState('Mary') // 1. 读取变量名为 name 的 state(参数被忽略)
// useEffect(persistForm) // 🔴 此 Hook 被忽略!
useState('Poppins') // 🔴 2 (以前为 3)。读取变量名为 surname 的 state 失败
useEffect(updateTitle) // 🔴 3 (以前为 4)。替换更新标题的 effect 失败
复制代码
React 不知道第二个 useState
的 Hook 应该返回什么。React 会觉得在该组件中第二个 Hook 的调用像上次的渲染同样,对应得是 persistForm
的 effect,但并不是如此。从这里开始,后面的 Hook 调用都被提早执行,致使 bug 的产生。ajax
若是咱们想要有条件地执行一个 effect,能够将判断放到 Hook 的_内部_:typescript
useEffect(function persistForm() {
// 👍 将条件判断放置在 effect 中
if (name !== '') {
localStorage.setItem('formData', name);
}
});
复制代码
use
开头吗?必须如此。这个约定很是重要。不遵循的话,因为没法判断某个函数是否包含对其内部 Hook 的调用,React 将没法自动检查你的 Hook 是否违反了 Hook 的规则。npm
不会。自定义 Hook 是一种重用_状态逻辑_的机制(例如设置为订阅并存储当前值),因此每次使用自定义 Hook 时,其中的全部 state 和反作用都是彻底隔离的。
useState
或者 useEffect
,每次调用 Hook,它都会获取独立的 state,是彻底独立的。react.docschina.org/docs/hooks-…
useState
调用中,要么每个字段都对应一个 useState
调用,这两方式都能跑通。useReducer
来管理它,或使用自定义 Hook。这是个比较罕见的使用场景。若是你须要的话,你能够 使用一个可变的 ref 手动存储一个布尔值来表示是首次渲染仍是后续渲染,而后在你的 effect 中检查这个标识。(若是你发现本身常常在这么作,你能够为之建立一个自定义 Hook。)
react.docschina.org/docs/hooks-…
function Example({ someProp }) {
function doSomething() {
console.log(someProp);
}
useEffect(() => {
doSomething();
}, []); // 🔴 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)
}
复制代码
要记住 effect 外部的函数使用了哪些 props 和 state 很难。这也是为何 一般你会想要在 effect 内部 去声明它所须要的函数。 这样就能容易的看出那个 effect 依赖了组件做用域中的哪些值:
function Example({ someProp }) {
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]); // ✅ 安全(咱们的 effect 仅用到了 `someProp`)
}
复制代码
只有 当函数(以及它所调用的函数)不引用 props、state 以及由它们衍生而来的值时,你才能放心地把它们从依赖列表中省略。下面这个案例有一个 Bug:
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
async function fetchProduct() {
const response = await fetch('http://myapi/product' + productId); // 使用了 productId prop
const json = await response.json();
setProduct(json);
}
useEffect(() => {
fetchProduct();
}, []); // 🔴 这样是无效的,由于 `fetchProduct` 使用了 `productId`
// ...
}
复制代码
推荐的修复方案是把那个函数移动到你的 effect 内部。这样就能很容易的看出来你的 effect 使用了哪些 props 和 state,并确保它们都被声明了:
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
useEffect(() => {
// 把这个函数移动到 effect 内部后,咱们能够清楚地看到它用到的值。
async function fetchProduct() {
const response = await fetch('http://myapi/product' + productId);
const json = await response.json();
setProduct(json);
}
fetchProduct();
}, [productId]); // ✅ 有效,由于咱们的 effect 只用到了 productId
// ...
}
复制代码
www.robinwieruch.de/react-hooks… codesandbox.io/s/jvvkoo8pq…
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
// 注意 async 的位置
// 这种写法,虽然能够运行,可是会发出警告
// 每一个带有 async 修饰的函数都返回一个隐含的 promise
// 可是 useEffect 函数有要求:要么返回清除反作用函数,要么就不返回任何内容
useEffect(async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
}, []);
return (
<ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul>
);
}
export default App;
复制代码
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(() => {
// 更优雅的方式
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
}, []);
return (
<ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul>
);
}
export default App;
复制代码
useMemo
自己也有开销。useMemo
会「记住」一些值,同时在后续 render 时,将依赖数组中的值取出来和上一次记录的值进行比较,若是不相等才会从新执行回调函数,不然直接返回「记住」的值。这个过程自己就会消耗必定的内存和计算资源。所以,过分使用 useMemo
可能会影响程序的性能。useMemo
前,应该先思考三个问题:
useMemo
的函数开销大不大? 有些计算开销很大,咱们就须要「记住」它的返回值,避免每次 render 都去从新计算。若是你执行的操做开销不大,那么就不须要记住返回值。不然,使用 useMemo
自己的开销就可能超太重新计算这个值的开销。所以,对于一些简单的 JS 运算来讲,咱们不须要使用 useMemo
来「记住」它的返回值。string
、 boolean
、null
、undefined
、number
、symbol
),那么每次比较都是相等的,下游组件就不会从新渲染;若是计算出来的是复杂类型的值(object
、array
),哪怕值不变,可是地址会发生变化,致使下游组件从新渲染。因此咱们也须要「记住」这个值。useMemo
。以确保当值相同时,引用不发生变化。useEffect 接收的函数,要么返回一个能清除反作用的函数,要么就不返回任何内容。而 async 返回的是 promise。 www.robinwieruch.de/react-hooks…