react Hook
概念:在不使用class组件的情况下,允许你使用state和react的其他特性
产生背景:在组件之间公用相同的逻辑往往很难,在以前的解决方案是:高阶组件和render props 但是这类方案需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。
你可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。
Hooks不要在循环,条件或嵌套函数中以及class组件中调用 Hook,只在最顶层使用hook
不要在普通的函数中调用hook,只在react函数中调用hook
import React, { Component,useState } from 'react'; import ReactDOM from 'react-dom' import PropTypes from 'prop-types'; function Example(){ // count变量的值是useState返回的第一个值 setCount是useState返回的第二个值
//数组解构 const [count,setCount] = useState(0) return ( <div> <p>you click {count} times</p> <button onClick={()=>setCount(count+1)}>点击我</button> </div> ) }
ReactDOM.render(<Example />,document.getElementById('root'))
为避免重新创建被忽略的初始 state,我们可以传一个 函数 给 useState
:
即 useState
(()=>0) 这就叫惰性初始化 不会多次初始化只会初始化一次
useRef不能传递一个函数来初始化 需要自己编写惰性初始化
function Image(props) {
const ref = useRef(null);
// ✅ IntersectionObserver 只会被惰性创建一次
function getObserver() {
if (ref.current === null) {
ref.current = new IntersectionObserver(onIntersect);
}
return ref.current;
}
// 当你需要时,调用 getObserver()
// ...
}
import React, { Component,useState,useEffect} from 'react'; import ReactDOM from 'react-dom' import PropTypes from 'prop-types'; function Example(){ const [count,setCount] = useState(0) // 每次渲染完毕后都会执行useEffect //类似于class组件的 componentDidMount和componentDidUpdate
//会在执行当前 effect 之前对上一个 effect 进行清除
//useEffect 在浏览器渲染和布局完成之后会执行effect useEffect(()=>{ console.log(123) document.title = `You clicked ${count} times`
//如果需要清除副作用 则需要返回一个函数来清除副作用,如果不需要清除副作用 则不需要返回一个函数 },[]) //如果第二个参数是一个空数组 useEffect在挂载和卸载的时候只会执行一次
//React 会等待浏览器完成画面渲染之后才会延迟调用useEffect
,因此会使得额外操作很方便。 return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count+1)}> Click me </button> </div> ) } //通常在Effect
的内部声明他所需要的函数
//这样就能容易的看出那个 effect 依赖了组件作用域中的哪些值: ReactDOM.render(<Example />,document.getElementById('root'))//
import React, { Component,useState,useEffect} from 'react'; import ReactDOM from 'react-dom' import PropTypes from 'prop-types'; function Counter({initialCount}) { const [count, setCount] = useState(initialCount); return ( <React.Fragment> Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> {/* */} </React.Fragment> ); } //上面更新状态类似调用下面的 this.setState class App extends React.Component{ constructor(props){ this.setState((state)=>({ })) } } ReactDOM.render(<Counter initialCount={0} />,document.getElementById('root'))
如果你的更新函数返回值与当前 state 完全相同,则随后的重渲染会被完全跳过。
useEffect的执行时机:等到浏览器完成布局和绘制之后,传给 useEffect
的函数会延迟调用
默认情况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发生变化,它就会被重新创建。销毁前一个effect 创建一个新的effect
然而,在某些场景下这么做可能会矫枉过正。比如,在上一章节的订阅示例中,我们不需要在每次组件更新时都创建新的订阅,而是仅需要在 source
prop 改变时重新创建。
要实现这一点,可以给 useEffect
传递第二个参数,它是 effect 所依赖的值数组。
useEffect( () => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source], );
useReducer
import React, { Component,PropTypes,useContext,useReducer } from 'react'; import ReactDOM from 'react-dom' import { ThemeContext,themes } from './components/ThemeContext'; // import ThemeButton from './components/ThemeButton' const initialState = {count: 0}; function reducer(state,action){ switch(action.type){ case 'increment': return {count:state.count+1} case 'decrement': return {count:state.count-1} default: throw new Error() } } function Counter(){ const [state,dispatch] = useReducer(reducer,initialState) return ( <React.Fragment> counter:{state.count} <button onClick={()=>dispatch({type:'decrement'})}>-</button> <button onClick={()=>dispatch({type:'increment'})}>+</button> </React.Fragment> ) } ReactDOM.render(<Counter />,document.getElementById('root'))
import React, { Component,PropTypes,useContext,useReducer } from 'react'; import ReactDOM from 'react-dom' import { ThemeContext,themes } from './components/ThemeContext'; // import ThemeButton from './components/ThemeButton' //懒初始状态 function init(initialState){ return { count:initialState } } // const initialState = {count: 0}; function reducer(state,action){ switch(action.type){ case 'increment': return {count:state.count+1} case 'reset': return init(action.payload) case 'decrement': return {count:state.count-1} default: throw new Error() } } function Counter({initialState}){ console.log(useReducer(reducer,initialState,init)) const [state,dispatch] = useReducer(reducer,initialState,init) return ( <React.Fragment> counter:{state.count} <button onClick={()=>dispatch({type:'reset','payload':initialState})}>reset</button> <button onClick={()=>dispatch({type:'decrement'})}>-</button> <button onClick={()=>dispatch({type:'increment'})}>+</button> </React.Fragment> )
//跳过 dispatch
//如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。(React 使用Object.is
比较算法 来比较 state。)
} ReactDOM.render(<Counter initialState={0} />,document.getElementById('root'))
import React, { Component,PropTypes,useContext,useReducer ,useEffect,useRef,useImperativeHandle} from 'react'; import ReactDOM from 'react-dom' import { ThemeContext,themes } from './components/ThemeContext'; // import ThemeButton from './components/ThemeButton' function FancyInput(props,ref){ const input = useRef(); useImperativeHandle(ref,()=>({ focus:()=>{ input.current.focus(); } })) return <input ref={input} {...props} /> } FancyInput = React.forwardRef(FancyInput) class App extends React.Component{ constructor(props){ super(props) this.inputRef = React.createRef() } componentDidMount() { this.inputRef.current.focus() } render(){ return ( <FancyInput ref={this.inputRef} /> ) } } ReactDOM.render(<App />,document.getElementById('root'))
利用hook对数据进行获取
import React, { useState, useEffect } from "react"; import ReactDOM from "react-dom"; import axios from 'axios'; function SearchResults(){ const [data,setData] = useState({hits:[]}) const [query,setQuery] = useState("react") // await axios('https://hn.algolia.com/api/v1/search?query=' + query); useEffect(()=>{ let ignore = false; async function fetchData(){ let result = await axios('https://hn.algolia.com/api/v1/search?query=' + query); console.log(3) if(!ignore) { console.log('1') setData(result.data) } } fetchData() return ()=>{ console.log('2') ignore = true; } },[query]) return ( <React.Fragment> <input value={query} onChange={(e)=>setQuery(e.target.value)} /> <ul> {data.hits.map((item)=>( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> </React.Fragment> ) } const rootElement = document.getElementById("root"); ReactDOM.render(<SearchResults />, rootElement);
获取前一次的props或state
import React, { useState, useEffect,useRef } from "react"; import ReactDOM from "react-dom"; import axios from 'axios'; function Counter() { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return <h1>Now: {count}, before: {prevCount} <button onClick={()=>setCount(count+1)}>+</button></h1>; } function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; } const rootElement = document.getElementById("root"); ReactDOM.render(<Counter />, rootElement);
useCallback的应用场景
useCallback
Hook 允许你在重新渲染之间保持对相同的回调引用以使得 shouldComponentUpdate
继续工作:
useMemo
Hook 使得控制具体子节点何时更新变得更容易,减少了对纯组件的需要。
还有一个以纯函数组件的优化方案。那就是React.memo. 他相当于一个高阶组件 class组件优化shouldcomponentupdate 用的是 React.Purcomponent
useReducer
Hook 减少了对深层传递回调的依赖
import React, { useState, useEffect,useRef,useCallback } from "react"; import ReactDOM from "react-dom"; import axios from 'axios'; //每当ref被添加到另外一个节点 react就会调用callback function App(){ const [height,setHeight] = useState(0) const measuredRef = useCallback(node=>{ if(node!=null){ setHeight(node.getBoundingClientRect().height) } }) return ( <React.Fragment> <h1 ref={measuredRef}>Hello, world</h1> <h1>The above header is {Math.round(height)}px tall</h1> </React.Fragment> ) } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);