第一版:将节点直接添加到根节点
当一个 ReactNode 类型的节点,没有在 render 函数内时,如何渲染到页面上呢?
就需要借助 react-dom/client 中的 createRoot 函数,同时在不需要的时候销毁所有内容。
所以,就搞一个 hook 函数,函数加载的时候节点被 render 到页面上,函数卸载的时候卸载所有内容。同时增加当节点内容变化时更新 DOM 的功能。
import React, { FC, useRef, useState, useEffect, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
type useRenderDocumentProps = (renderNode: ReactNode) => void
const useRenderDocument: useRenderDocumentProps = (renderNode) => {
const rootRef = useRef<HTMLDivElement | null>(null)
const rootInstanceRef = useRef<Root | null>(null)
const prevNode = useRef(renderNode)
useEffect(() => {
if (rootInstanceRef.current && prevNode.current !== renderNode) {
rootInstanceRef.current.render(renderNode)
prevNode.current = renderNode
}
}, [renderNode])
useEffect(() => {
if (!rootInstanceRef.current) {
const root = document.createElement('div')
root.className = 'render-root'
document.body.appendChild(root)
rootRef.current = root
rootInstanceRef.current = createRoot(root)
rootInstanceRef.current.render(renderNode)
}
return () => {
console.log('销毁')
if (rootRef.current) {
rootRef.current.remove()
rootRef.current = null
rootInstanceRef.current?.unmount()
rootInstanceRef.current = null
}
}
}, [])
}
export default useRenderDocument
第二版:将节点添加指定位置
当节点需要使用已经定义好的 useReducer createContext,这个方法就存在问题,就会找不到对应的内容。
所以当有一段js逻辑和一个组件(多数是弹窗形式)而且需要先执行js以后才调用组件(打开弹窗组件),且不想破坏原有UI,这时候可以定义个hook,将js逻辑和组件封装到一个hook里面,并返回。返回的组件随便找个地方放一下就行,js逻辑在使用的时候调用。
下面是一个批量下载的例子。
import { useRef, type ReactNode } from 'react'
type uploadProps = (params?: {
title?: ReactNode,
templateLabel?: string,
templateFileName?: string,
uploadLabel?: string,
tips?: ReactNode,
successTips?: ReactNode,
}) => Promise<void>
type uploadRecordsProps = (params?: {
title?: ReactNode,
}) => Promise<void>
type useBatchImportDataProps = (code: string) => [
{
upload: uploadProps,
uploadRecords: uploadRecordsProps
},
ReactNode
]
const useBatchImportData: useBatchImportDataProps = (code) => {
const uploadRef = useRef<any>(null)
const uploadRecordsRef = useRef<any>(null)
const upload:uploadProps = async (params = {}) => {
uploadRef.current.open(params)
}
const uploadRecords:uploadRecordsProps = async (params = {}) => {
uploadRecordsRef.current.open(params)
}
return [
{
upload,
uploadRecords
},
<>
<BatchImportData ref={uploadRef} code={code}/>
<BatchImportData.Records ref={uploadRecordsRef} code={code}/>
</>
]
}
export default useBatchImportData
使用方法
const StoreList = () => {
const [batchImport, batchImportNode] = useBatchImportData('code')
const toBatchUpload = () => {
batchImport.upload({
title: '批量创建',
templateLabel: '↓ 下载导入模板',
templateFileName: '批量导入模板.xlsx',
tips: <>
<div>注意事项:</div>
<div>1、必填项错误的数据无法创建</div>
<div>2、为逐一创建,上传文件到完全创建完成需要时间,可稍后点击<a>【批量创建记录】</a>查看单个的创建成功/失败情况</div>
</>,
successTips: <>已提交,正在创建中,完成后可在<a>【批量创建记录】</a>查看创建结果</>
})
}
const toBatchUploadRecords = () => {
batchImport.uploadRecords({
title: '批量创建记录'
})
}
return (
batchImportNode
)
}