Hooks 是 React 函數組件內一類特殊的函數(一般以 "use" 開頭,好比 "useState"),使開發者可以在 function component 裏依舊使用 state 和 life-cycles,以及使用 custom hooks 複用業務邏輯。javascript
當前react常常碰見的問題:java
bind
,this
指向不明確Hooks讓咱們更好地進行代碼邏輯複用。 函數組件能夠很好地進行邏輯複用,可是函數組件是無狀態的,只能做爲【純組件】展現,不能處理局部state。Hooks讓函數組件擁有了局部state,能夠處理狀態邏輯。react
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } 複製代碼
Hooks會返回一個Tuple,結構爲[value, setValue]
。web
這兩個返回值分別對應以前react裏的算法
咱們還能夠在函數中同時使用多個state編程
function ExampleWithManyStates() {
// Declare multiple state variables!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
複製代碼
以前更新state中值,經過this.setState({ fruit: 'orange' })
,會對以前的state和更新後的state進行合併。redux
而使用Hooks,會將state進行拆分爲一個個value,更新後,直接使用新值替換,不會進行state的合併。[state,setState]的結構也讓值的更新邏輯更加清晰。react-native
配合
React.createContext({})
使用,在組件間的共享狀態數組
示例:bash
const AppContext = React.createContext({});
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar/>
<Messages/>
</div>
</AppContext.Provider>
複製代碼
而後在Navbar組件內就能夠直接使用AppContext
const Navbar = () => {
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
}
複製代碼
用來簡單替代redux作狀態管理,可是無法提供中間件(middleware)和時間旅行(time travel)等複雜場景
示例:
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 (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
複製代碼
effectHooks讓咱們能夠在函數組件內使用生命週期方法,咱們能夠在這裏更新DOM,獲取數據等具備'反作用'的行爲。effect Hook會在組件每次render後執行,ruturn的函數會在組件卸載時執行,若要讓effect hook只在組件首次加載時執行,能夠傳入一個空數組做爲第二個參數,也能夠在數組中指定依賴項,只有依賴項改變時,effectHooks纔會執行。
import { useState, useEffect } from 'react';
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
}
複製代碼
自定義Hook是一個以'use'開頭的javascript函數,能夠調用其餘的Hooks,從而進行邏輯封裝,複用代碼。例如:
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
複製代碼
其餘函數組件就可使用:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
*********************************
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
複製代碼
Hooks必須在函數頂層使用,不能用於條件,循環,嵌套中。 Hooks會逐步徹底替代class組件,目前還沒法支持getSnapshotBeforeUpdate和componentDidCatch生命週期的功能。
首先咱們須要整理下react的數據更新和視圖渲染機制。以前都是經過調用setState
來更改數據,頁面進行re-render,咱們先來看看setState
是如何工做的。
React的基礎架構分爲三個部分:react基礎包、react-reconciler、renderer渲染模塊
react基礎模塊: react 基礎 API 及組件類,組件內定義 render 、setState 方法和生命週期相關的回調方法,相關 API 以下:
const React = {
Children: {},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
};
複製代碼
renderer渲染模塊: 針對不一樣宿主環境採用不一樣的渲染方法實現,如 react-dom, react-webgl, react-native, react-art, 依賴 react-reconciler模塊, 注入相應的渲染方法到 reconciler 中,react-dom 中相關的 API 以下:
const ReactDOM: Object = {
createPortal,
findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {},
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {},
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {},
unstable_renderSubtreeIntoContainer() {},
unmountComponentAtNode(container: DOMContainer) {},
unstable_batchedUpdates: DOMRenderer.batchedUpdates,
unstable_deferredUpdates: DOMRenderer.deferredUpdates,
unstable_interactiveUpdates: DOMRenderer.interactiveUpdates,
flushSync: DOMRenderer.flushSync,
unstable_flushControlled: DOMRenderer.flushControlled,
}
複製代碼
react-reconciler核心模塊:負責調度算法及 Fiber tree diff, 鏈接 react基礎包 和 renderer 模塊,注入 setState 方法到 component 實例中,在 diff 階段執行 react 組件中 render 方法,在 patch 階段執行 react 組件中生命週期回調並調用 renderer 中注入的相應的方法渲染真實視圖結構。
setState
定義在React.Component中,可是React包中只是定義API,並無具體實現邏輯。相似的還有createContext()
等大多數功能都是在‘渲染器’中實現的。react-dom、react-dom/server、 react-native、 react-test-renderer、 react-art都是常見的渲染器。因此咱們在使用react新特性的時候,react和react-dom都須要更新。
setState
在React.Component中定義updater
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
複製代碼
在具體的渲染器中會本身實現updater:
// React DOM 內部
var classComponentUpdater = {
isMounted: isMounted,
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst);
var currentTime = requestCurrentTime();
var expirationTime = computeExpirationForFiber(currentTime, fiber);
var update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback$1(callback, 'setState');
}
update.callback = callback;
}
flushPassiveEffects();
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
enqueueReplaceState: function (inst, payload, callback) {
//註釋了
},
enqueueForceUpdate: function (inst, callback) {
//註釋了
}
};
複製代碼
Hooks也是使用了相同的設計,使用了‘dispatcher’對象,來代替‘updater’。咱們調用useState()
時,都被轉發給當前的dispatcher。 updater字段和dispatcher對象都是使用依賴注入的通用編程原則的形式。在這兩種狀況下,渲染器將諸如setState之類的功能的實現「注入」到通用的React包中,以使組件更具聲明性。
useState是如何讓無狀態的函數組件能夠保存狀態,更新視圖,和this.setState的更新有啥區別?
React中有一個基礎對象ReactElement,它由React.createElement()建立的
React.createElement(
type,
[props],
[...children]
)
//舉個例子
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
//徹底等價於
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
const element = {
$$typeof: REACT_ELEMENT_TYPE, // 是不是普通Element_Type
// Built-in properties that belong on the element
type: type, // 咱們的組件,好比`class MyComponent`
key: key,
ref: ref,
props: props,
children: children,
// Record the component responsible for creating this element.
_owner: owner,
};
複製代碼
這是一個vdom節點,在React16以前,React會根據這個vdom節點生成真實的dom結構。React16以後,官方引入了Fiber結構,react的基本架構也變得更加複雜了。React會將vdom節點對應爲一個Fiber節點,Fiber節點的結構:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null; // 就是ReactElement的`$$typeof`
this.type = null; // 就是ReactElement的type
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.memoizedState = null;
this.updateQueue = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.firstContextDependency = null;
// ...others
}
複製代碼
其中的this.updateQueue
用來存儲setState的更新隊列,this.memoizedState
來儲存組件內的state狀態,類組件中是用來存儲state對象的,在Hooks中用來存儲Hook對象。
//類組件中更新state的update對象
var update = {
expirationTime: expirationTime,
tag: UpdateState,
payload: null,
callback: null,
next: null,
nextEffect: null
};
//函數組件中的Hook對象
{
baseState,
next,
baseUpdate,
queue,
memoizedState
};
//類組件中的updateQueue的結構
var queue = {
baseState: baseState,
firstUpdate: null,
lastUpdate: null,
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null
};
//每新增一個update就加入到隊列中
function appendUpdateToQueue(queue, update) {
// Append the update to the end of the list.
if (queue.lastUpdate === null) {
// Queue is empty
queue.firstUpdate = queue.lastUpdate = update;
} else {
queue.lastUpdate.next = update;
queue.lastUpdate = update;
}
}
複製代碼
Hooks的更新分紅兩步,初始化時進行mount操做,更新時進行update操做。分別經過HooksDispatcherOnMountInDEV和HooksDispatcherOnUpdateInDEV兩個對象來存儲全部Hooks更新的函數。
HooksDispatcherOnMountInDEV = {
readContext: function (context, observedBits) {
},
useCallback: function (callback, deps) {
},
useContext: function (context, observedBits) {
},
useEffect: function (create, deps) {
},
useImperativeHandle: function (ref, create, deps) {
},
useLayoutEffect: function (create, deps) {
},
useMemo: function (create, deps) {
},
useReducer: function (reducer, initialArg, init) {
},
useRef: function (initialValue) {
},
useState: function (initialState) {
var hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
var queue = hook.queue = {
last: null,
dispatch: null,
eagerReducer: basicStateReducer,
eagerState: initialState
};
var dispatch = queue.dispatch = dispatchAction.bind(null,
// Flow doesn't know this is non-null, but we do. currentlyRenderingFiber$1, queue); return [hook.memoizedState, dispatch]; }, useDebugValue: function (value, formatterFn) { } }; //其中的dispatch即爲咱們調用的‘setState’函數,核心代碼爲: function dispatchAction(fiber, queue, action) { //註釋了******* var update = { expirationTime: renderExpirationTime, action: action, eagerReducer: null, eagerState: null, next: null }; if (renderPhaseUpdates === null) { renderPhaseUpdates = new Map(); } renderPhaseUpdates.set(queue, update); } HooksDispatcherOnUpdateInDEV = { //註釋了********** useState: function (initialState) { currentHookNameInDev = 'useState'; var prevDispatcher = ReactCurrentDispatcher$1.current; ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; try { return updateState(initialState); } finally { ReactCurrentDispatcher$1.current = prevDispatcher; } }, }; 複製代碼
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function updateReducer(reducer, initialArg, init) {
// 註釋了**********
var firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
var newState = hook.memoizedState;
var update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the // priority because it will always be the same as the current // render's.
var _action = update.action;
newState = reducer(newState, _action);
update = update.next;
} while (update !== null);
}
複製代碼
update對象中的action就是使用setState的參數,update會被加入到更新queue中,在全部‘update’都收集完後,會觸發react的更新。更新時,執行到函數組件中的useState,而後拿到Hook對象,取出其中的queue對象,依次進行更新,獲得新的state保存到memoizedState上,並返回,更新視圖。
其中memoizedState
是用來記錄這個useState
應該返回的結果的,而next
指向的是下一次useState
對應的`Hook對象。
例:
function FunctionalComponent () {
const [state1, setState1] = useState(1)
const [state2, setState2] = useState(2)
const [state3, setState3] = useState(3)
}
複製代碼
執行的順序爲:
hook1 => Fiber.memoizedState
state1 === hoo1.memoizedState
hook1.next => hook2
state2 === hook2.memoizedState
hook2.next => hook3
state3 === hook2.memoizedState
複製代碼
next是依賴上一次的state的值,若是某個useState沒有執行,這個對應關係就亂了。因此,react規定使用Hooks時,必須在根做用域下使用,不能用於條件語句,循環中。
整理下Hooks具備的特徵:
可使用數組結構來模擬實現:
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
}
console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']
// click the 'Fred' button
console.log(state); // After-click: ['Fred', 'Yardley']
複製代碼