本文从一个小点, 思考了一下, 一目千行的查阅了下 hook 源码. 然后光顾了 react 禁术, 就像 Harry Potter 的幻影显形咒一样.
你没事吧?这种感觉需要慢慢适应。
——阿不思·邓布利多
Story
1. Origin
如下组件, a, b, c 判断条件要写三个, 如何写一遍?
export const Origin: React.FC = () => {
const a = true;
const b = true;
const c = true;
return (
<div>
{(a || b || c) && (
<div>
<h5>title</h5>
{a && <div>a</div>}
{b && <div>b</div>}
{c && <div>c</div>}
</div>
)}
</div>
);
};
2. 抽了一个组件, 判断 children
如下,判断ReactChildNode, 如全部为空, 则返回 null。就不需要写一遍全部条件 a || b || c 了,
三思而后行, 看起来比原始代码复杂太多
const Wrapper: React.FC = ({ children }) => {
const children = (props as any)?.children
console.info('children', children)
if (
!children ||
(Array.isArray(children) && children.every((item) => !item))
) {
return null
}
return (
<div>
<h5>title</h5>
{children}
</div>
)
}
export const V1: React.FC = () => {
const a = true
const b = false
const c = false
return (
<Wrapper>
{a && <div>a</div>}
{b && <div>b</div>}
{c && <div>c</div>}
</Wrapper>
)
}
3. 当我遇到了一个空组件?
想依然能够判断。
const Null: React.FC = () => null
export const V2: React.FC = () => {
return (
<Wrapper>
<Null />
<Null />
</Wrapper>
)
}
4. 函数式 React Node 是一个函数, 我运行一下它
console.info(<Null />) 看到 {... type: fn Null(), props: {} ...}
于是,
const Wrapper: React.FC = ({ children }) => {
const children = (props as any)?.children
console.info('children', children)
if (
!children ||
(Array.isArray(children) && children.every((item) => {
if(!item) {
return true
}
if(typeof item?.type === 'function') {
return !item.type(item.props)
}
return false
}))
) {
return null
}
return (
<div>
<h5>title</h5>
{children}
</div>
)
}
[此处有运行效果, <Null />节点可被忽略],
5. 虽然可以, 隐优巨大, 如过 <Null /> 里有 hook
const Null: React.FC<{ hi?: string }> = ({ hi = '_' }) => {
useEffect(() => {
console.info('null <--', hi)
return () => {
console.info('null -->', hi)
}
}, [])
return null
}
[图1. 当我运行,children 全为空时, 可以被过滤掉, 但运行了一次]
[图2. 当我运行,children 不全为空时, 运行了两次]
6. 我能拿到消除副作用的函数吗?如果可以,我提前执行它去。
先看看 useEffect 是什么? 找到了它 react/src/ReactHooks.js#L95
@flow
export function useEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
const dispatcher = resolveDispatcher()
return dispatcher.useEffect(create, deps)
}
dispatcher 是什么东西? useEffect 调用的结果会不会存在它身上? 🤔️
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
function resolveDispatcher() {
return ReactCurrentDispatcher.current;
}
--- ./ReactCurrentDispatcher.js
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher),
};
export default ReactCurrentDispatcher;
咦, 有个 export default, 可以的
[图1 , 我使用编辑器输入 React.ReactCu... 让它提示出来, 什么都没有]
[图2 , 我点到 node_modules/@types/react/index.d.ts 全文搜索🔍 ReactCurrentDispatcher, 什么都没有]
[图3 , 我打开 node_modules/react/cjs/react.development.js, 🔍搜索到了 !!]
var ReactSharedInternals = {
ReactCurrentDispatcher: ReactCurrentDispatcher,
...
};
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactSharedInternals;
用这个 API 会被炒鱿鱼🦑, 😯 ! 而且, 似乎我拿不到 return 的内容
6.1 但, 看到 .current 的我转念一想
同步方法执行过程中, 我替换掉 dispatcher.current, 完后再替换回来, 稳! 于是
export const apparate = function (exec: () => any) {
const x = (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
const origin = x.ReactCurrentDispatcher.current
x.ReactCurrentDispatcher.current = new Proxy(
{},
{
get: () => () => undefined,
},
)
const result = exec()
x.ReactCurrentDispatcher.current = origin
return result
}
export const Wrapper: React.FC<T> = (props) => {
const children = (props as any)?.children
console.info('children', children)
if (
!children ||
(Array.isArray(children) &&
children.every((item) => {
if (!item) {
return true
}
if (typeof item?.type === 'function') {
try {
return !apparate(() => item.type(item.props))
} catch (error) {
console.warn(' execute error')
return false
}
}
return false
}))
) {
return null
}
return (
<div>
<h5>title</h5>
{children}
</div>
)
}
👀看, 执行 function component function 的时候, 原地幻影移形了一下.
[图 1. 我执行代码, children 全空时, 没有执行]
[图 2. 我执行代码, children 不全空时, 执行了一次]
好, 超出预期, 比原计划的主动消除副作用好多了
完
Play
封装起来玩
My Style:
/** 消隐无踪(Deletrius), 如果子元素全空, 则自身消失 */
export function selfOffHoc<T>(Wrapper: React.FC<T>) {
const SelfOffWrapper: React.FC<T> = (props) => {
const children = (props as any)?.children
if (
[].concat(children).every((item: any) => {
if (!item) {
return true
}
if (typeof item?.type === 'function') {
try {
return !apparate(() => item.type(item.props))
} catch (error) {
console.warn(' execute error')
return false
}
}
return false
})
) {
return null
}
return <Wrapper {...props}>{children}</Wrapper>
}
return SelfOffWrapper
}
用法:
// const ExtraWrapper: React.FC<{ loading?: boolean }> = ...
const ExtraWrapper = selfOffHoc<{ loading?: boolean }>(({ children }) => (
<View style={{ marginRight: '0px' }}>
<View>Below:</View>
<View>{children}</View>
</View>
))
// 以上, 对比常规方式, 似乎没有动锁进
export default function () {
return (
<ExtraWrapper>
{null && <a>1</a>}
{false && <b>2</b>}
{0 && <code>3</code>}
<Null />
</ExtraWrapper>
)
}
结:
- 纯展示性组件, 无副作用组件, 可使用此方法减少冗余一遍的判断条件
- 在 useEffect 改变自身状态的, 不适用
额外建议 -- 不要在 ..._BE_FIRED 的肩膀上挑战它, 搞async 幻影移形大概会写出一整套 anti - react hooks, 或许会路过@vue/reactivity.
It does not to do dwell on dreams, and forget to live. ——Dumbledore
哈利,人不能活在梦里,不要依赖梦想而忘记生活。 ——邓布利多