如何使用React Hook
Posted 学好前端
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用React Hook相关的知识,希望对你有一定的参考价值。
一、认识Hook
Hook 是⼀个特殊的函数,它可以让你“钩⼊” React 的特性。例如, useState 是允许你在 React 函数组件中添加 state 的 Hook。
什么时候我会⽤Hook?
如果你在编写函数组件并意识到需要向其添加⼀些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使⽤ Hook。
不使用Hook存在的问题?
1.组件变得复杂和难以维护:
2.满天class导致的热重载和性能问题:
3.class自生具有的复杂度和组件嵌套过深props层级传递
二、使用useState Hook
import React,{useState} from "react"
export default function Hookpage(props) {
// 声明⼀个叫 “count” 的 state 变量,初始化为0
const [count,setCount] = useState(0);
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button
</div>
);
}
三、使⽤ Effffect Hook
Effffect Hook 可以让你在函数组件中执⾏副作⽤操作。数据获取,设置订阅以及⼿动更改 React 组件中的 DOM 都属于副作⽤。不管你知不知道这些操作,或是“副作⽤”这个名字,应该都在组件中使⽤过它们。
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
// 声明⼀个叫 “count” 的 state 变量,初始化为0
const [count, setCount] = useState(0);
// 与 componentDidMount 和 componentDidUpdate相似
useEffect(() => {
// 更新 title
document.title = `You clicked ${count} times`;
});
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
</div>
);
}
在函数组件主体内(这⾥指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录⽇志以及执⾏其他包含副作⽤的操作都是不被允许的,因为这可能会产⽣莫名其妙的 bug 并破坏 UI 的⼀致性。
使⽤ useEffect 完成副作⽤操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执⾏。你可以把 effffect 看作从 React 的纯函数式世界通往命令式世界的逃⽣通道。
默认情况下,effffect 将在每轮渲染结束后执⾏,但你可以选择让它 在只有某些值改变的时候 才执⾏(https://zh-hans.reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect)。
1、effffect 的条件执⾏
默认情况下,effffect 会在每轮组件渲染完成后执⾏。这样的话,⼀旦 effffect 的依赖发⽣变化,它就会被重新创建。
要实现这⼀点,可以给 useEffect 传递第⼆个参数,它是 effffect 所依赖的值数组。更新后的示例如下:
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
// 声明⼀个叫 “count” 的 state 变量,初始化为0
const [count, setCount] = useState(0);
const [date, setDate] = useState(new Date())
// 与 componentDidMount 和 componentDidUpdate相似
useEffect(() => {
// 更新 title
document.title = `You clicked ${count} times`;
}, [count]);
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
}, []);
return (
<div>
<h3>HookPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{date.toLocaleTimeString()}</p>
</div>
);
}
此时,只有当 useEffffect第⼆个参数数组⾥的数值 改变后才会重新创建订阅。
2、清除effffect
通常,组件卸载时需要清除 effffect 创建的诸如订阅或计时器 ID 等资源。要实现这⼀点, useEffect函数需返回⼀个清除函数,以防⽌内存泄漏,清除函数会在组件卸载前执⾏。
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
3、⾃定义Hook
有时候我们会想要在组件之间重⽤⼀些状态逻辑。⽬前为⽌,有两种主流⽅案来解决这个问题:⾼阶组件和 render props(https://zh-hans.reactjs.org/docs/render-props.html)。⾃定义Hook 可以让你在不增加组件的情况下达到同样的⽬的。
import React, { useState, useEffect, useMemo } from "react";
export default function CustomHookPage(props) {
//定义⼀个叫count的state变量,初始化为0
const [count, setCount] = useState(0);
//和didMount、didUpdate类似
useEffect(() => {
console.log("count effect");
// 只需要在count发⽣改变的时候执⾏就可以啦
document.title = `点击了${count}次`;
}, [count]);
return (
<div>
<h3>⾃定义Hook</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<p>{useClock().toLocaleTimeString()}</p>
</div>
);
}
//⾃定义hook,命名必须以use开头
function useClock() {
const [date, setDate] = useState(new Date());
useEffect(() => {
console.log("date effect");
//只需要在didMount时候执⾏就可以了
const timer = setInterval(() => {
setDate(new Date());
}, 1000);
//清除定时器,类似willUnmount
return () => clearInterval(timer);
}, []);
return date;
}
四、使用useMemo
把“创建”函数和依赖项数组作为参数传⼊ useMemo ,它仅会在某个依赖项改变时才重新计算memoized 值。这种优化有助于避免在每次渲染时都进⾏⾼开销的计算。
import React, { useState, useMemo } from "react";
export default function UseMemoPage(props) {
const [count, setCount] = useState(0);
const expensive = useMemo(() => {
console.log("compute");
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
//只有count变化,这⾥才重新执⾏
}, [count]);
const [value, setValue] = useState("");
return (
<div>
<h3>UseMemoPage</h3>
<p>expensive:{expensive}</p>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
</div>
);
}
五、使用useCallback
把内联回调函数及依赖项数组作为参数传⼊ useCallback ,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使⽤引⽤相等性去避免⾮必要渲染(例如 shouldComponentUpdate )的⼦组件时,它将⾮常有⽤。
import React, { useState, useCallback, PureComponent } from "react";
export default function UseCallbackPage(props) {
const [count, setCount] = useState(0);
const addClick = useCallback(() => {
let sum = 0;
for (let i = 0; i < count; i++) {
sum += i;
}
return sum;
}, [count]);
const [value, setValue] = useState("");
return (
<div>
<h3>UseCallbackPage</h3>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={value} onChange={event => setValue(event.target.value)} />
<Child addClick={addClick} />
</div>
);
}
class Child extends PureComponent {
render(){
console.log("child render");
const { addClick } = this.props;
return (
<div>
<h3>Child</h3>
<button onClick={() => console.log(addClick())}>add</button>
</div>
);
}
}
useCallback(fn, deps) 相当于 useMemo(() => fn, deps) 。
注意:
依赖项数组不会作为参数传给“创建”函数。虽然从概念上来说它表现为:所有“创建”函数中引⽤的值都应该出现在依赖项数组中。未来编译器会更加智能,届时⾃动创建数组将成为可能。
六、使用useContext 和 useReducer
// App.js
import React from "react";
import UseReducerPage from "./pages/UseReducerPage";
import UseContextPage from "./pages/UseContextPage";
import { MyContext } from "./AppContext";
function App() {
return (
<div className="App">
<MyContext.Provider value={{themeColor:"red"}} >
<UseReducerPage />
<UseContextPage />
</MyContext.Provider>
</div>
);
}
export default App;
// AppContext.js
import react from "react";
export const MyContext = React.createContext();
// UseContextPage.js
import React, { useState, useEffect, useContext } from "react";
import { MyContext } from "../AppContext";
export default function UseContextPage(props) {
const ctx = useContext(MyContext )
console.log('ctx',ctx)
return (
<div className={ctx.themeColor}>
<h3>UseContextPage</h3>
</div>
);
}
// UseReducerPage.js
import React, { useState, useEffect} from "react";
function fruitReducer(state= [],action) {
switch(action.type) {
case "INIT":
case "REPLACE":
return [...action.payload];
case 'ADD':
return [...state,action.payload];
default:
return state;
}
}
export default function UseReducerPage(props) {
const [fruits,dispatch] = useReducer(fruitReducer,[]);
useEffect(()=>{
setTimeout(()=>{
dispatch({type:"INIT",playload:["apple","banana"]});
},1000)
},[])
return (
<div className={ctx.themeColor}>
<h3>UseReducerPage</h3>
<AddFruit addFruit={(newFruit)=>dispatch({type:'ADD',
payload:newFruit})></AddFruit>
<FruitList fruits={fruits} setFruit={(newFruitList)=>
dispatch({type:'REPLACE',payload:newFruitList})}/>
</div>
);
}
function AddFruit({addFruit}) {
const [name,setName] = useState("");
return (
<div>
<input value={name} onChange={(e)=>setName(e.target.value)}/>
<button onClick={()=>addFruit(name)}>add</button>
</div>
)
}
fucntion FruitList ({fruits,setFruit}){
const delFruit = delIndex=>{
const tem = [...fruits]
tem.splice(delIndex,1)
setFruit(tem);
}
return (
<ul>
{
fruits.map((fruit,index) => {
return <li key={index} onClick={()=>delFruit(indnex)}>{fruit}</li>
})
}
</ul>
);
}
七、使用useRef
1、用于绑定DOM,
使用类组件,类似React.createRef,
class App extends React.Component {
refInput = React.createRef();
componentDidMount() {
this.refInput.current && this.refInput.current.focus();
}
render() {
return <input ref={this.refInput} />;
}
}
2、使用函数组件
function App() {
const refInput = React.useRef(null);
React.useEffect(()=> {
refInput.current && refInput.current.focus();
},[])
return <input ref={refInput } />;
}
二、父组件调用子组件方法
1、使用类组件
import React, { Component } from "react";
export default class ParentComponent extends Component {
refChild = React.createRef();
setChildText = () => {
if (this.refChild.current) {
this.refChild.current.setText("text upddated by parent component")
}
}
render() {
return (
<>
{/* 父组件引用时,直接使用ref即可绑定子组件实例 */}
<ChildComponent ref={this.refChild} />
<button onClick={this.setChildText}>set text by parent</button>
</>
)
}
}
class ChildComponent extends Component {
state = {
text: null
}
setText(text = null) {
this.setState({ text });
}
render() {
return <p> {this.state.text}</p >
}
}
2、使用函数组件
需要配合uselmperativeHandle使用
import React, { useState, useRef, forwardRef, useImperativeHandle } from "react";
function Child(props, ref) {
const [text, setText] = useState(null);
// 该 hook需要定义抛出给父组件的可以使用的api方法
// 相当与代理了子组件的方法
useImperativeHandle(ref, () => ({
setTextByParent(text = "") {
setText(text);
}
}));
return <p>text:{text}</p>;
}
// 函数组件需要使用forwardRef包裹
const ForwardChild = forwardRef(Child);
export default function Parent() {
const ref = useRef(null);;
return (
<>
<ForwardChild ref={ref} />
<button
onClick={() => {
ref.current && ref.current.setTextByParent("text updated by parent component");
}}
>
sett text by parent
</button>
</>
);
}
总结:
Hook 就是 javascript 函数,但是使⽤它们会有两个额外的规则:
1、只能在函数最外层调⽤ Hook。不要在循环、条件判断或者⼦函数中调⽤。
2、只能在 React 的函数组件中调⽤ Hook。不要在其他 JavaScript 函数中调⽤。(还有⼀个地⽅可以调⽤ Hook —— 就是⾃定义的 Hook 中)
以上是关于如何使用React Hook的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 react-hook-form 在 React 中存储无线电组的状态
如何使用 React Hook 在 React 组件中引用和加载 Javascript 文件