Hook简介
Hook是React16.8的新增特性,它可以在你不编写class的情况下使用state以及其他的React特性
- Hooks是什么?为了拥抱正能量函数式
- Hooks带来的变革,让函数组件有了状态和其他的React特性,可以替代class
没有破坏性改动
- 完全可选。无需重写任何已有代码就可以在一些组件中尝试Hook
- 100%向后兼容。Hook不包含任何破坏性改动
- 现在可用。Hook发布于v16.8
没有计划从React中移除class
Hook不会影响你对React的理解,恰恰相反,Hook为已知的React概念提供了更直接的API:props,state,context,refs以及生命周期
Hook解决了什么问题
- 组件之间复用状态逻辑很难
React没有提供将可复用行为‘附加’到组件的途径(例如,把组件连接到store),比如render props
和高阶组件
,但这类方案需要重新组织你的组件结构,这可能会很麻烦。这说明了一个更深层次的问题:React需要为共享状态逻辑提供更好的原生途径
你可以使用Hook从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook使你在无需修改组件结构的情况下复用状态逻辑,这使得在组件间共享Hook变得更便捷
- 复杂组件变得难以理解
我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期尝尝包含一些不相关的逻辑。例如,组件尝尝在componentDidMount
和componentDidUpdate
中获取数据,但是同一个componentDidMount
中也可能包含很多其它的逻辑,如设置事件监听,而后需要在componentWillUnmount
中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起,如此很容易产生bug,并且导致逻辑不一致
这时候很多人将React与状态管理库结合使用,但是,这往往会引入很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难
为了解决这个问题,Hook将组件中相互关联的部分拆分为更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分,你还可以使用reducer来管理组件的内部状态,使其更加可预测
- 难以理解的class
除了代码复用和代码管理会遇到困难,还需要去理解Javascript中this的工作方式和class类,对于class组件和函数组件的差异也需要去区分两者的使用场景。另外class不能很好的压缩,并且会使热重载出现不稳定的情况
为了解决这些问题,Hook使你在非class的情况下使用更多的React特性。从原则上讲,React组件一直更像函数,而Hook则拥抱了函数,同时也没有牺牲React的精神原则,Hook提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术
Hook API
基础的Hook
- useState
- useEffect
- useContext
注意:useState返回一个数组[state,setState],是因为使用数组解构方面灵活命名,如果返回对象则属性名是写死的,不利于开发者命名
额外的Hook
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
自定义Hook
- 以use开头的函数
详情请查阅官方文档Hook简介
useState简单实现
实现一个useState简单版本
测试文件index.js
function FunctionComponent(props) {
const [count, setCount] = useState(0);
console.log("count", count, typeof count);
return (
<div className="border">
<p>{props.name}</p>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
);
}
....省略啦
hook之所以要按顺序放在函数的最顶层,是因为hook是用链表结构来存储的,存在函数fiber节点的memoizedState指针上
// hook的链表结构
hook = {
memoizedState: //状态值
queue: // 数组,处理批量在空闲的时候
next: // 指向下一个hook
};
全部代码文件react-dom.js
/**
* vnode 虚拟DOM
* node 真实DOM
*/
import { Placement, Update } from "./const";
// 根节点 fiber
let wipRoot = null;
let currentRoot = null;
function render(vnode, container) {
// console.log("vnode", vnode);
/* // vnode->node
const node = createNode(vnode);
// node 插入到container中
container.appendChild(node); */
wipRoot = {
type: "div",
props: { children: { ...vnode } },
stateNode: container
};
nextUnitWork = wipRoot;
// console.log("netUnitWork", nextUnitWork);
}
function isStringOrNumber(str) {
return typeof str === "string" || typeof str === "number";
}
// 根据vnode,生成node
function createNode(workInProgress) {
let node = document.createElement(workInProgress.type);
updateNode(node, {}, workInProgress.props);
return node;
}
// 更新原生标签的属性,如className,href,id,(style,事件等)
function updateNode(node, prevVal, nextVal) {
Object.keys(prevVal).forEach(k => {
if (k === "children") {
// 有可能是文本
if (isStringOrNumber(prevVal[k])) {
node.textContent = "";
}
} else if (k.slice(0, 2) === "on") {
const eventName = k.slice(2).toLocaleLowerCase();
node.removeEventListener(eventName, prevVal[k]);
} else {
if (!(k in nextVal)) {
node[k] = "";
}
}
});
Object.keys(nextVal)
// .filter(k => k !== "children")
.forEach(k => {
if (k === "children") {
// 有可能是文本
if (isStringOrNumber(nextVal[k])) {
node.textContent = nextVal[k] + "";
}
} else if (k.slice(0, 2) === "on") {
const eventName = k.slice(2).toLocaleLowerCase();
node.addEventListener(eventName, nextVal[k]);
} else {
node[k] = nextVal[k];
}
});
}
// 原生标签
function updateHostComponent(workInProgress) {
// 修身
// 构建真实dom节点
if (!workInProgress.stateNode) {
workInProgress.stateNode = createNode(workInProgress);
}
// 齐家
// 协调子节点
reconcileChildren(workInProgress, workInProgress.props.children);
// console.log("workInProgress", workInProgress);
}
// 文本
function updateTextComponent(workInProgress) {
if (!workInProgress.stateNode) {
workInProgress.stateNode = document.createTextNode(workInProgress.props);
}
}
// 函数组件
function updateFunctionComponent(workInProgress) {
// 当前正在工作的fiber以及hook的初始化
currentlyRenderingFiber = workInProgress;
currentlyRenderingFiber.memoizedState = null; // 存取Hook
workInProgressHook = null;
const { type, props } = workInProgress;
const children = type(props);
reconcileChildren(workInProgress, children);
}
// 类组件
function updateClassComponent(workInProgress) {
const { type, props } = workInProgress;
// 先实例化
const instance = new type(props);
const children = instance.render();
reconcileChildren(workInProgress, children);
}
// <>Fragment节点
function updateFragmentComponent(workInProgress) {
reconcileChildren(workInProgress, workInProgress.props.children);
}
// 最假的吧,最简单的也是协调
function reconcileChildren(workInProgress, children) {
if (isStringOrNumber(children)) {
return;
}
let newChildren = Array.isArray(children) ? children : [children];
let previousNewFiber = null;
let oldFiber = workInProgress.alternate && workInProgress.alternate.child;
for (let i = 0; i < newChildren.length; i++) {
let child = newChildren[i];
let same =
child &&
oldFiber &&
// child.key === oldFiber.key &&
child.type === oldFiber.type;
let newFiber;
if (same) {
// 节点复用
newFiber = {
type: child.type, // 类型
props: { ...child.props }, // 属性
stateNode: oldFiber.stateNode, // 如果是原生标签代表dom节点,如果是类组件就代表实例
child: null, // 第一个子节点fiber
sibling: null, // 下一个兄弟节点 fiber
return: workInProgress, // 父节点
alternate: oldFiber, // 上一次fiber
flags: Update
};
}
if (!same && child) {
// 构建fiber节点
newFiber = {
type: child.type, // 类型
props: { ...child.props }, // 属性
stateNode: null, // 如果是原生标签代表dom节点,如果是类组件就代表实例
child: null, // 第一个子节点fiber
sibling: null, // 下一个兄弟节点 fiber
return: workInProgress, // 父节点
alternate: null, // 上一次fiber
flags: Placement
};
}
if (!same && oldFiber) {
// 删除
}
if (isStringOrNumber(child)) {
newFiber.props = child;
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
if (i === 0) {
// 第一个子fiber
workInProgress.child = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
/**
* fiber数据结构
* type
* props
* key
* stateNode 如果是原生标签代表dom节点,如果是类组件代表实例
* child 第一个子节点
* sibling 下一个兄弟节点
* return 指向父节点
* alternate 上一次fiber
*/
// workInProgress当前正在进行的中的fiber
function performNextUnitWork(workInProgress) {
// step1 执行当前任务
const { type } = workInProgress;
if (isStringOrNumber(type)) {
// 原生标签
updateHostComponent(workInProgress);
} else if (typeof type === "function") {
if (type.prototype.isReactComponent) {
// 类组件
updateClassComponent(workInProgress);
} else {
// 函数组件
updateFunctionComponent(workInProgress);
}
} else if (typeof type === "undefined") {
// 文本
updateTextComponent(workInProgress);
} else {
// Fragment
updateFragmentComponent(workInProgress);
}
// step2 并且返回下一个任务(深度优先遍历)
if (workInProgress.child) {
// 有孩子返回孩子
return workInProgress.child;
}
let nextFiber = workInProgress;
while (nextFiber) {
// 有兄弟返回兄弟,没有返回叔叔
if (nextFiber.sibling) {
return nextFiber.sibling;
}
// 叔叔就是爸爸的兄弟
nextFiber = nextFiber.return;
}
}
// 下一个要执行的任务
let nextUnitWork = null; // fiber
function workLoop(IdleDeadline) {
while (nextUnitWork && IdleDeadline.timeRemaining() > 1) {
// 执行当前任务,并且返回下一个任务
nextUnitWork = performNextUnitWork(nextUnitWork);
}
// 没有任务就提交
if (!nextUnitWork && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
// 在浏览器的空闲时间段内调用的函数排队
requestIdleCallback(workLoop);
function commitRoot() {
commitWorker(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
}
function commitWorker(workInProgress) {
if (!workInProgress) {
return;
}
// 更新自己
let parentNodeFiber = workInProgress.return;
while (!parentNodeFiber.stateNode) {
parentNodeFiber = parentNodeFiber.return;
}
let parentNode = parentNodeFiber.stateNode;
if (workInProgress.flags & Placement && workInProgress.stateNode) {
parentNode.appendChild(workInProgress.stateNode);
} else if (workInProgress.flags & Update && workInProgress.stateNode) {
updateNode(
workInProgress.stateNode,
workInProgress.alternate.props,
workInProgress.props
);
}
// 更新子节点
commitWorker(workInProgress.child);
// 更新下一个兄弟节点
commitWorker(workInProgress.sibling);
}
// 当前正在工作的fiber
let currentlyRenderingFiber = null;
// 当前正在工作的hook
let workInProgressHook = null;
// 一个hook函数
export function useState(initialState) {
let hook;
// 判断是否是组件初次渲染
if (currentlyRenderingFiber.alternate) {
// 不是初次渲染,是更新阶段
currentlyRenderingFiber.memoizedState =
currentlyRenderingFiber.alternate.memoizedState;
if (workInProgressHook) {
} else {
// 是第一个hook
workInProgressHook = currentlyRenderingFiber.memoizedState;
}
hook = workInProgressHook;
} else {
// 初次渲染
hook = {
memoizedState: initialState, // 状态值
queue: [], // 批量更新数组存取
next: null // 下一个hook
};
if (workInProgressHook) {
// 不是第一个hook
hook = workInProgressHook;
} else {
// 是第一个hook,挂到fiber节点上
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
}
}
// 模拟批量处理
hook.queue.forEach(action => (hook.memoizedState = action));
const dispatch = action => {
hook.queue.push(action);
wipRoot = {
type: currentRoot.type,
stateNode: currentRoot.stateNode,
props: currentRoot.props,
alternate: currentRoot
};
nextUnitWork = wipRoot;
// console.log("action", action);
};
// 指向下一个hook
workInProgressHook = workInProgressHook.next;
return [hook.memoizedState, dispatch];
}
// eslint-disable-next-line
export default { render };