在ReactConf上,React团队提出了一种使用React实现交互式组件的新方法,称为Hooks。
他们发布了一个RFC版本,在本文中,我们将研究如何实现这样的功能。
开始准备工作:
npx create-react-app react-hooks
cd react-hooks
npm i react@next react-dom@next
npm start
让我们创建一个新组件signin.js:
import React from 'react';
function Signin(props) { return <button onClick={props.isSignin ? props.Signout : props.Signin} > {props.isSignin ? "Signout" : "Sign in"}</button> }
export default Signin;
|
counter.js
import React from 'react';
export default (props) => {
return ( <div> <h1> {props.value}</h1> <button onClick={props.Increment} >Increment</button> <button onClick={props.Decrement} >Decrement</button> </div> )
}
|
Hooks钩子是一种帮助我们使用react特征的功能
例如,在下面的代码中,useState钩子用于在功能组件中添加状态。
App.js
import React, { useState } from 'react'; import './App.css'; import Signin from './signin'; import Counter from './counter';
function App(props) {
console.log(useState(0));
return ( <div className="App"> <header className="App-header"> </header> </div> );
}
export default App;
|
在上面的代码中,我们通过日志输出初始状态0的useState钩子。
useState挂钩返回具有两个元素的数组,这两个元素是当前状态和用于更新状态的函数。让我们使用数组解构的两个元素。
import React, { useState } from 'react'; import './App.css'; import Signin from './signin'; import Counter from './counter';
function App(props) {
const [value, Setvalue] = useState(0);
function Incvalue() { return Setvalue(value + 1) }
function Decvalue() { return Setvalue(value - 1) }
return ( <div className="App"> <header className="App-header"> <Counter value={value} Increment={Incvalue} Decrement={Decvalue} /> </header> </div> );
}
export default App;
|
这里使用一个简单的函数调用useState,useState钩子用于在功能组件中添加状态。我们能获得当前状态,我们可以更改状态,使用新状态值重新渲染组件。Incvalue和Decvalue函数能够改变value的数值状态。
自定义React钩子
自定义反应钩只是javascript函数,以“use”一词开头。
让我们创建自己的反应自定义钩子。
status.js
import { useState } from 'react';
function useSigninStatus(status) {
const [isSignin, setSignin] = useState(status);
function Signin() { setSignin(true) }
function Signout() { setSignin(false) } return { isSignin, Signin, Signout, } }
export default useSigninStatus;
|
如何使用自定义反应钩?以下代码是两种钩子一起使用
import React, { useState, useEffect } from 'react'; import './App.css'; import Signin from './signin'; import Counter from './counter'; import useSigninStatus from './status';
function App(props) {
const [value, Setvalue] = useState(0);
let status = useSigninStatus(false);
function Incvalue() { return Setvalue(value + 1) }
function Decvalue() { return Setvalue(value - 1) }
return ( <div className="App"> <header className="App-header"> <Signin {...status} /> <Counter isSignin={status.isSignin} value={value} Increment={Incvalue} Decrement={Decvalue} /> </header> </div> );
}
export default App;
|
源码:代码库
实际的钩子内部函数如下所示,只是原理展示:
let globalHooks; function useState(defaultValue) { let hookData = globalHooks.get(useState);
if (!hookData) hookData = { calls: 0, store: [] };
if (hookData.store[hookData.calls] === undefined) hookData.store[hookData.calls] = defaultValue;
let value = hookData.store[hookData.calls];
let calls = hookData.calls; let setValue = function(newValue) { hookData.store[calls] = newValue; hookData.render(); };
hookData.calls += 1; globalHooks.set(useState, hookData);
return [value, setValue]; }
|
一步步分析:
- 在第一次调用时应该返回defaultValue。
- 它试图从globalHooks全局变量中获取最后一次运行的状态。在调用组件函数之前通过run函数设置Map对象。Map应该有上次运行后的数据,否则需要创建自己的数据hookData。
- hookData.store是一个数组,用于存储上次调用的hookData.calls值,该值用于跟踪此函数被我们的组件调用的程度。
- 通过hookData.store[hookData.calls],我们可以抓住调用的最后一个存储值; 如果它不存在我们必须使用defaultValue。
- setValue回调用于单击按钮时更新我们的数据值,例如。当点击一个按钮,会调用calls,因此它知道setState函数调用属于哪个calls。然后使用hookData.render回调,启动所有组件的重新呈现。
- hookData.calls计数器增加。
- hookData被存储在globalHooks变量中,因此组件函数返回后,可通过render函数使用。