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']
复制代码