最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 舍弃redux,基于swr的通用数据流方案

    正文概述 掘金(fridaymarket)   2021-02-02   1326

    前言

    在对新项目进行技术选型的时候,针对数据流做了一些思考记录。

    大量的文章分析了redux、mobx等数据流方案的优缺点,在我看来,方案的选择离不开业务。

    而在react hooks 之后,又涌现了很多优秀的解决方案如:SWR等。

    作为一个开发者而言,始终面临着一个问题,究竟当前数据是否应该放入状态管理库或者仅仅只在组件中使用? 而我们始终秉承着思想:

    • 当数据不需要共享,那它就应该只属于某个组件,保持它的独立性,不需要使用状态去管理它,用完抛弃了就好。
    • 拒绝样板代码,太多的冗余代码增加了应用的复杂度

    在全面拥抱react hooks之后,redux和上面的思想相悖。我们需要一个更简单、精简与视图更加紧密的请求器(本文默认大家已经对SWR有一定的了解)

    首先,我们来看下在react function component中,如何去获取数据:

    function Posts() { 
      const [posts, setPosts] = useState(); 
      const  [params, setParams] = useState({id: 1}); 
      
      useEffect(() => { 
        fetchPosts(params).then(data => setPosts(data));
      }, [params]); 
      
      if (!posts) { return <div>Loading posts...</div>; } 
       return ( 
         <ul>{posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> 
       ); 
    }
    

    可以看到,在hooks的写法下,请求数据需要额外的逻辑来保存数据流的状态,这种方式又带来了更多的冗余,缺少了部分逻辑扩展。

    鉴于此我们可以想到,那可以把上面请求逻辑封装成自定义hook简化逻辑(伪代码):

    const useRequest = (api, params) => {
    
        const [posts, setPosts] = React.useState()
        const [loading, setLoading] = React.useState(false)
    
        const fetcher = (params) => {
            setLoading(true)
    	const realParmas = JSON.parse(params)
            api(realParmas).then(data => {
                setPosts(data)
                setLoading(false)
            })
        }
        React.useEffect(() => {
            fetcher(params)
        }, [params])
    
        return {
            data: posts,
            loading
        }
    }
    
    
    function Posts() {
        const { data, loading }  = useRequest('/user', JSON.stringify({params: 123}))
    
        return ( 
        	<ul> {data.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> 
        );
    }
    
    

    通过封装,我们把业务简化,减少了冗余代码,逻辑更清晰。

    上面的封装带来了几个特性, - 依赖请求 。useRequest的params更新时,useRequest会重新请求。 - 请求状态管理。loading 状态

    但是一个商业化的产品,需要的特性不仅仅如此,我们可能需要对数据进行缓存、对api进行统一管理、错误重试。面对typescript项目还需要支持对api参数的推导,带来更友好的代码提示。

    而SWR覆盖了大部分我们所需要的特性,下面我们基于SWR进行封装,保留SWR的特性下,增加SWR所缺少的: - api进行统一管理 - typescript类型推导 - 支持动态依赖请求

    Api进行统一管理

    我们在管理api的时候,需要写很多的样板代码来定义api,所以需要一个生成函数来简化它。

    import { AxiosRequestConfig  } from 'axios'
    
    export type ApiConfig<Params = any, Data = any> = AxiosRequestConfig & {
        url: string
        method?: Method
        params?: Params
        data?: Params
        _response?: Data
        [x: string]: any
    }
    
    export type Service<Params = any, Data = any> = (_params?: any) => ApiConfig<Params, Data>
    
    
    export const createGetApi = <Params = any, Data = any>(apiConfig: ApiConfig) => {
        return (params: Params): Service<Params, Data> => (_params = {}) => {
            const nextParams = { ...params, ..._params }
    
            return {
                ...apiConfig,
                params: nextParams,
                method: 'get'
            }
        }
    }
    
    export const createPostApi = <Params = any, Data = any>(apiConfig: ApiConfig) => {
        return (data: Params): Service<Params, Data> => {
            return () => ({
                ...apiConfig,
                data,
                method: 'post'
            })
        }
    }
    

    我们基于axios定义了两个Api生成器。生成器所做的工作就是定义api的基本配置和参数及返回值的类型定义,接下来我们来使用它:

    interface GetUserParmas {  
      id: number
    }
    interface GetUserResponse {  
      id: number
      userName: string
    }
    const userModule = {
      getUser: createGetApi<GetUserParmas, GetUserResponse>({url: ‘/user’})
    }
    

    可以看到,这样就可以很方便的定义api,我们在用个custom hook来统一管理api

    const useApiSelector = () => {
        return {
            userModule
        }
    }
    

    好了我们完成了第一步统一管理Api,接下来我们要对swr进行封装,让它支持上诉所需要的特性:

    基于SWR封装新特性

    function useAsync<Params = any, Data = any>(
    	# api生成器生成的api
        service: ServiceCombin<Params, Data>,
        # swr的配置
        config: ConfigInterface<Data>
    ): BaseResult<Params, Data>
    function useAsync<Params = any, Data = any>(service: ServiceCombin<Params, Data>, config) {
    
        //  swr全局配置
        const globalConfig = React.useContext(Provider)
    	
        const requestConfig = Object.assign({}, DEFAULT_CONFIG, globalConfig as any, config || {})
    
        //传入参数fetcher 保证与swr 一致
        if (requestConfig.fetcher) {
            requestConfig['fetcher'] = fetcherWarpper(requestConfig.fetcher)
        }
    
        const configRef = React.useRef(requestConfig)
        
        configRef.current = requestConfig
    
       // 取出api的配置
        const serviceConfig = genarateServiceConfig(service)
    	
        const serviceRef = React.useRef(serviceConfig)
    
        serviceRef.current = serviceConfig
    	// 将api的参数转化成swr的 key
        const serializeKey = React.useMemo(() => {
            if (!serviceRef.current) return null
    
            try {
                return JSON.stringify(serviceRef.current)
            } catch (error) {
                return new Error(error)
            }
        }, [serviceRef.current])
    
        if (serializeKey instanceof Error) {
            invariant(false, '[serializeKey] service must be object')
        }
    
        const response = useSWR<Response<Data>>(serializeKey, configRef.current)
    
        const getParams = React.useMemo(() => {
            if (!serviceRef.current) return undefined
    
            return (serviceRef.current?.params || serviceRef.current?.data || {}) as Params
        }, [serviceRef.current])
    
    	// 对返回值进行验证,可以忽略
        const disasterRecoveryData = React.useMemo(() => {
            return getDisasterRecoveryData<Data>(response?.data)
        }, [response.data])
    
        return {
            ...response,
            ...disasterRecoveryData,
            params: getParams
        }
    }
    
    

    好了,我们完成对SWR的封装,根据传入的api定义类型推导出response。来和之前对比一下目前方案:

    const App  = () => {
      const apis = useApiSelector()
      // 在typescirpt 应用中,就可以自动推导出getUser需要的参数类型
      const { data, isValidating }  = useRequest(api.getUser({id: 1}))
        
      return (
        <div>{data}</div>
      ) 
    }
    

    总结:

    以上,主要是把自己对SWR与业务结合的一些思考阐述出来。选择一个数据流管理方案的时候,并不总是选择一个大家都在用,而且需要一个更贴切自身业务的。

    上方的代码只是作为思路的引导,完整的解决方案已经上传到github,大家可以到仓库查看完整的代码,完整的代码包含了更多的特性,包括支持antd的分页等特性。


    起源地下载网 » 舍弃redux,基于swr的通用数据流方案

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元