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 ( <> <Child 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…
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…
从 Preact 中了解 React 组件和 hooks 基本原理表
2019年了,整理了N个实用案例帮你快速迁移到React Hooks