1、自定义Hook的基本使用
自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性。
function CustomHookLifeDemo01() {
useLoggingLifeCycle('CustomHookLifeDemo01')
return (
<div>
<h2>CustomHookLifeDemo01</h2>
<Home />
<Profile />
</div>
)
}
// 必须以use开头的function才能使用 useEffect函数
function useLoggingLifeCycle(name) {
useEffect(() => {
console.log(`${name}组件被创建`);
return () => {
console.log(`${name}组件被销毁掉`);
}
})
}
2、自定义useContext
App.js
export const UserContext = createContext()
export const ThemContext = createContext()
export const TokenContext = createContext()
user-context.js
import { useContext } from "react";
import { TokenContext, UserContext } from "../App";
export function useUserContext() {
const user = useContext(UserContext);
const token = useContext(TokenContext);
return [user, token];
}
useContext的Demo
import React, { useCallback, useContext } from 'react'
import { TokenContext, UserContext } from '../App'
import { useUserContext } from '../hooks/user-hook';
function CustomContextShare() {
const [user, token] = useUserContext()
console.log(user, token);
return (
<div>
<h2>CustomContextShare</h2>
</div>
)
}
export default CustomContextShare
3、自定义useScrollPosition
scroll-position-hook.js
import { useEffect, useState } from "react";
export function useScrollPosition() {
const [scrollPosition, setScrollPosition] = useState(0);
useEffect(() => {
const handleScroll = () => {
setScrollPosition(window.scrollY)
console.log(window.scrollY);
}
document.addEventListener("scroll", handleScroll)
return (() => {
document.removeEventListener("scroll", handleScroll)
})
})
return scrollPosition;
}
CustomScrollPositionhook.js
import React, { useEffect, useState } from 'react'
import { useScrollPosition } from '../hooks/scroll-position-hook'
function CustomScrollPositionhook() {
const scrollPosition = useScrollPosition()
return (
<div style={{padding: "1000px 0"}}>
<h2 style={{position: "fixed", left: 0, right: 0, top: 0}}>CustomScrollPositionhook {scrollPosition}</h2>
</div>
)
}
export default CustomScrollPositionhook
4、练习
local-store-hook.js
import { useEffect, useState } from "react";
export function useLocalStorage(key) {
const [name, setName] = useState(() => {
const name = JSON.parse(window.localStorage.getItem(key));
return name;
})
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(name));
}, [name])
return [name, setName];
}
CustomDataStorageHook.js
import React, { useEffect, useState } from 'react'
import { useLocalStorage } from '../hooks/local-store-hook';
function CustomDataStorageHook() {
const [name, setName] = useLocalStorage("name")
return (
<div>
<h2>CustomDataStorageHook-{name}</h2>
<button onClick={e => setName("codeWhy")}>设置name</button>
</div>
)
}
export default CustomDataStorageHook
useState源码
// ReactFiberHooks.new.js line 346
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
if (__DEV__) {
hookTypesDev =
current !== null
? ((current._debugHookTypes: any): Array<HookType>)
: null;
hookTypesUpdateIndexDev = -1;
// Used for hot reloading:
ignorePreviousDependencies =
current !== null && current.type !== workInProgress.type;
}
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// The following should have already been reset
// currentHook = null;
// workInProgressHook = null;
// didScheduleRenderPhaseUpdate = false;
// TODO Warn if no hooks are used at all during mount, then some are used during update.
// Currently we will identify the update render as a mount because memoizedState === null.
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
// Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
// so memoizedState would be null during updates and mounts.
if (__DEV__) {
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
// This dispatcher handles an edge case where a component is updating,
// but no stateful hooks have been used.
// We want to match the production code behavior (which will use HooksDispatcherOnMount),
// but with the extra DEV validation to ensure hooks ordering hasn't changed.
// This dispatcher does that.
ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
}
} else {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null // 保存state状态
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
let children = Component(props, secondArg);
// Check if there was a render phase update
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// Keep rendering in a loop for as long as render phase updates continue to
// be scheduled. Use a counter to prevent infinite loops.
let numberOfReRenders: number = 0;
do {
didScheduleRenderPhaseUpdateDuringThisPass = false;
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
numberOfReRenders += 1;
if (__DEV__) {
// Even when hot reloading, allow dependencies to stabilize
// after first render to prevent infinite render phase updates.
ignorePreviousDependencies = false;
}
// Start over from the beginning of the list
currentHook = null;
workInProgressHook = null;
workInProgress.updateQueue = null;
if (__DEV__) {
// Also validate hook order for cascading updates.
hookTypesUpdateIndexDev = -1;
}
ReactCurrentDispatcher.current = __DEV__
? HooksDispatcherOnRerenderInDEV
: HooksDispatcherOnRerender;
children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
}
// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrancy.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
if (__DEV__) {
workInProgress._debugHookTypes = hookTypesDev;
}
// This check uses currentHook so that it works the same in DEV and prod bundles.
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
if (__DEV__) {
currentHookNameInDev = null;
hookTypesDev = null;
hookTypesUpdateIndexDev = -1;
}
didScheduleRenderPhaseUpdate = false;
invariant(
!didRenderTooFewHooks,
'Rendered fewer hooks than expected. This may be caused by an accidental ' +
'early return statement.',
);
return children;
}
网友评论