🔥 前言
最近遇到一个需求,需要复用原来的组件页面(页面比较复杂涉及很多弹窗操作,表单和二级详情页),新旧页面组件只是调用接口不同,新旧两套接口,其他只是略微文案上的差异。后期的话新页面肯定会有较大变动,业务着急所以临时复用旧页面。
🤔 问题描述
由于后面可能会有较大变动,这期新加复用页面也可能会下掉,如何有效的修改,并且确保后续下掉这期功能不会影响旧原来功能;
如何能做到更方便下掉这期功能删除代码,且不会影响原来的功能逻辑?
💡 解决方案
假设新增的组件是B,需要引用原来的组件A。
方案一:使用useContext
代码示例
javascript
const apiMapA = {
list : 'xxxx/A/list',
query: 'xxxx/A/query',
edit: 'xxxx/A/edit',
...
}
const apiMapB = {
list : 'xxxx/B/list',
query: 'xxxx/B/query',
edit: 'xxxx/B/edit',
...
}
Type ApiMapType = typeof apiMapA | typeof apiMapB
const context = useContext<ApiMapType|null>(null)
const APIMapProvider:FC<{value:ApiMapType}> = ({value,...props})=>{
return <Context.Provider value={value}>
{props.children}
</Context.Provider>
}
const useContextAPIMap = ()=>{
const context = useContext()
if(!context){
return message.error('useContextAPIMap 必须包裹在 APIMapProvider下')
}
return context
}
javascript
const B = ()=>{
return <APIMapProvider value={apiMapB}>
<A/>
</APIMapProvider>
}
javascript
const A = ()=>{
return <ContextProvider value={apiMapA}>
<SubApp/>
</ContextProvider>
}
叶子组件中调用接口
javascript
const SubApp = ()=>{
const apiMap = useContextAPIMap()
const handleQuery =()=>{
....
const http.get(apiMap.query)
}
return <>
{....}
<Button onClick={handleQuery}>查询</Button>
</>
}
优点:
- 优点1: 文件夹隔离组件范围,删除比较方便,删除B组件文件夹,不会影响A组件内容;
- 优点2: 每套api对象定义链路没有动态路径,后面IDE追踪引用api地方,且api调用不会出错;
缺点:
- 缺点1:叶子组件的每处api调用的地方都需要修改成取出useContextAPI去调用,改动量大,比较麻烦;
- 缺点2:后须删除新增逻辑,想要复原原来A组件的直接调用api方式比较麻烦;
方案二:http使用拦截器
代码示例
由于两套接口的具有特殊规律,所以可以用拦截器去替换请求,需要替换时候正则必须要严格,需要特别小心:
比如:https://www.jsfund.cn/A/query
替换:https://www.jsfund.cn/B/query
这里替换思路:应该是路径开头是/A/
的请求;不能简单写错包含A
替换成B
就行;
另外,由于拦截器是全局的作用范围,所以需要在离开页面的时候去卸载掉拦截器。
最后,内部的新旧文案的判断,可以使用useRouteMatch 或者useSWR做swr缓存去区分。
javascript
const B = ()=>{
// 入口处写入区分页面模块缓存
useModuleType('B')
useEffect(()=>{
const id = axios.interceptors.request.use((response)=>{
return response.url.replace(/(https:\/\/\w+)\/A\// ,'$1/B/')
})
return ()=>{
axios.interceptors.request.eject(id)
}
},[])
return <>
<SubApp/>
</>
}
自定义指定fetcher
函数获取缓存结果,由于是手动写入缓存值,不需要开启验证功能;
javascript
const useModuleType = (moduleType: 'A' | 'B') =>{
// swr key随便指定唯一就行,
const {data} = useSWR('_pageB_ModuleType',{
fetcher: ()=>Promise.resolve(moduleType)
autorun:false,
refreshInterval: false,
revalidateOnFocus: false,
revalidateIfStale: false
})
return data
}
优点:
- 优点1:对比方法一,修改量极小,不用修改每处api调用的地方;
- 优点2:后面恢复A模块非常方便,删除代码比较少;
缺点:
- 缺点1:依赖替换api接口之间有比较明显的逻辑规律;
- 缺点2:容易错误过滤替换别的模块接口,需要严格限制api接口替换规则;
📝 总结
最终我还是使用方案二,因为这次需要接口替换规则明显,用拦截器非常方便。如果接口替换规则无任何规则的话可能就比较麻烦了。抛砖引玉,如果大家有更好的方案欢迎留言评论😊。