最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 2021最新React Hooks+TS项目最佳实践

    正文概述 掘金(FelixCoder)   2020-11-25   449

    前言:写了千篇一律的React项目。突然想玩点新的花样。平时用JS比较多。但团队配合,TS才是最好的方式。所以这个小项目采用TS。再结合RecoilJs + Swr组合来打造数据处理层。 单元测试说很重要,但真正实行的公司确很少。配合Enzyme+Jtest 来测试react组件,确实很爽。所以将整个过程记录下来。 我们一起学习吧。

    一.关键知识扫盲

    上面提到了几个关键的框架,我下面分别简单介绍一些,具体的细节可以去他们的官方GitHub上去了解。

    1.Recoiljs facebook针对 react hooks新出的状态管理框架,比较轻,好上手。几大优点:灵活共享 state,并保持高性能,高效可靠地根据变化的 state 进行计算,Atom操作只是对可订阅可变state影响,避免全局rerender。还有 Cross-App Observation 跨页面的状态传递。

    2. Swr 是提供远程数据请求的React Hooks库,它也能很好的结合axios一起使用。主要特点有:自动间隔轮询,自动重试请求,避免写async和await这种语法糖,也没有回调,结合React hook是比较好用。

    3. Enzyme 是 Airbnb 开源的专为 React 服务的测试框架,它的 Api 像 Jquery 一样灵活,因为 Enzyme 是用 cheerio 库解析 html,cheerio 经常用于 node 爬虫解析页面,因此也被成为服务端的 Jquery。Enzyme 实现了一套类似于 Jquery 的 Api,它的核心代码是构建了一个 ReactWrapper 类或者 ShallowWrapper 包裹 React 组件,区别在于 ShallowWrapper 的类只会渲染组件的第一层,不渲染自组件,所以是一种浅渲染。当然它还提供了Mount方法,可以做深渲染。

    二. 构建项目基本结构

    1.快速创建基本的webpack配置

    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const path = require('path');
    
    module.exports = {
      mode: 'development',
      entry: './src/entry.tsx',
      devtool: "inline-source-map",
      devServer:{
        historyApiFallback: true,
      },
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
      },
      resolve: {
        extensions: ['.ts', '.tsx', '.js']
      },
      module: {
        rules: [{
          test: /\.css$/,
          use: [
            {loader: 'style-loader'},
            {loader: 'css-loader'}
          ]
        }, {
          test: /\.tsx?$/,
          loader: 'ts-loader',
          exclude: /node_modules/
        }]
      },
      plugins: [
        new HtmlWebpackPlugin()
      ]
    }
    

    2.定义package.json,并且安装相关依赖

    {
      "scripts": {
        "dev": "webpack-dev-server --open",
        "test": "jest"
      },
      "dependencies": {
        "@types/axios": "^0.14.0",
        "@types/enzyme": "^3.10.8",
        "@types/mockjs": "^1.0.3",
        "@types/react-router-dom": "^5.1.6",
        "axios": "^0.21.0",
        "enzyme": "^3.11.0",
        "enzyme-adapter-react-16": "^1.15.5",
        "jest": "^26.6.3",
        "mockjs": "^1.1.0",
        "react": "16.9.0",
        "react-dom": "16.9.0",
        "react-router-dom": "^5.2.0",
        "recoil": "^0.1.2",
        "swr": "^0.3.9"
      },
      "devDependencies": {
        "@babel/preset-env": "^7.12.7",
        "@babel/preset-react": "^7.12.7",
        "@types/enzyme-adapter-react-16": "^1.0.6",
        "@types/jest": "^26.0.15",
        "@types/node": "12.7.2",
        "@types/react": "16.9.2",
        "@types/react-dom": "16.9.0",
        "babel-jest": "^26.6.3",
        "css-loader": "3.2.0",
        "html-webpack-plugin": "3.2.0",
        "identity-obj-proxy": "^3.0.0",
        "react-addons-test-utils": "^15.6.2",
        "react-recoil-hooks-testing-library": "^0.0.8",
        "react-test-renderer": "^17.0.1",
        "source-map-loader": "0.2.4",
        "style-loader": "1.0.0",
        "ts-jest": "^26.4.4",
        "ts-loader": "6.0.4",
        "typescript": "^4.1.2",
        "webpack": "4.39.3",
        "webpack-cli": "3.3.7",
        "webpack-dev-server": "3.8.0"
      }
    }
    

    然后在yarn install 即可

    3. 构建项目的目录结构和基本的页面

    2021最新React Hooks+TS项目最佳实践

    三. 构建接口请求和mock数据

    1.先创建一个mock接口,在Mock文件夹下面创建一个Index.ts

    import Mock from 'mockjs'
    
    //建立一个mocker数据
    Mock.mock("get/options.mock",{
        code:0,
        "data|9-19":[
            {label: /[a-z]{3,5}/, "value|+1": 99,},
        ]
    })
    

    解释一下 “data|9-19” 返回的data数据是一个数组,最小9个,最大19个。

    label:/[a-z]{3,5}/ : 代表里面的label值是3到5字母,并且随机生成。

    value|+1:99 : 代表从value的值从99开始自增

    mockjs非常强大,可以构建丰富的数据类型和结构,大家可以去官网了解详细用法。

    然后在entry.tsx入口文件中引入这个mock,才能起作用

    //entry.tsx page
    import React from 'react'
    import ReactDOM from 'react-dom'
    import './Mock/Index' //引入mock数据
    
    import App from './App';
    var reactapp = document.createElement("div");
    document.body.appendChild(reactapp);
    ReactDOM.render(<App/>, reactapp);
     
    

    2.结合axios构建Swf请求封装

    import useSWR, { ConfigInterface, responseInterface } from 'swr'
    import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
    interface Repository {
      label: string;
      value: string;
    }
    
    //创建axios实例
    const api = axios.create({
    });
    type JsonData = {
      code: number,
      data: Repository[]
    }
    
    export type GetRequest = AxiosRequestConfig
    //定义返回类型
    export interface Return<JsonData, Error>
      extends Pick<
      responseInterface<AxiosResponse<JsonData>, AxiosError<Error>>,
      'isValidating' | 'revalidate' | 'error'
      > {
      data: JsonData | undefined
      response: AxiosResponse<JsonData> | undefined
      requestKey: string
    }
    
    export interface Config<JsonData = unknown, Error = unknown>
      extends Omit<
      ConfigInterface<AxiosResponse<JsonData>, AxiosError<Error>>,
      'initialData'
      > {
      initialData?: JsonData
    }
    //useRequest 封装swr的请求hooks函数
    export default function useRequest<Error = unknown>(
      request: GetRequest,
      { initialData, ...config }: Config<JsonData, Error> = {}
    ): Return<JsonData, Error> {
      //如果是开发模式,这里做环境判断,url后面加上".mock"就会走mock数据
      if (process.env.NODE_ENV === "development") {
        request.url += ".mock"
      }
      const requestKey = request && JSON.stringify(request);
    
      const { data: response, error, isValidating, revalidate } = useSWR<
        AxiosResponse<JsonData>,
        AxiosError<Error>
      >(requestKey, () => api(request), {
        ...config,
        initialData: initialData && {
          status: 200,
          statusText: 'InitialData',
          config: request,
          headers: {},
          data: initialData
        }
      })
      // if(response?.data.code !==0){ //handler request error
      //     throw  "request wrong!"
      // }
      return {
        data: response?.data,
        requestKey,
        response,
        error,
        isValidating,
        revalidate
      }
    }
    

    useRequest 的作用其实很简单,就是在hooks组件里面做react请求。他可以这样使用

     const { data: data } = useRequest<Repository[]>({
        url: "get/options"
     })
    

    data 是axios返回的数据 response数据 。

    这里还可以这样使用,调出requestKey

     const { data: data,requestKey } = useRequest<Repository[]>({
        url: "get/options"
     })
    

    requestKey 的作用在于,可以使用mutate手动的更新数据并且执行数据修改

    //如果这样可以手动刷新数据
    mutate(requestKey) 
    
    //如果这样,那么会执行post请求到原来请求,修改数据
    mutate(requestKey,{...newData}) 
    
    //这样那么会先调updateFetch promise修改数据后,然后更新requestKey对应的请求
    mutate(requestKey,updateFetch(newData)) 
    

    看起来swr还比较强大。 更多用法,请去他的官方github网站上了解。这里就不细讲了 。

    四.利用RecoilJS进行状态管理

    1.创建state数据

    import { atom } from "recoil"
    export interface Repository {
      label: string;
      value: string;
    }
    export const OptionsState = atom<Repository[] | undefined>({
      key: "options",
      default: []
    })
    export const SelectedState = atom<string[]>({
      key: "selectedValues",
      default: []
    })
    

    atom是 recoil创建state 最基本的操作, 其实这里还有一个selector , 它的功能要比atom稍微强大一些

    const productCount = selector({
      key: 'productCount',
      get: ({get}) => {
        const products = get(productAtom)
        return products.reduce((count, productItem) => count + productItem.count, 0)
      },
      set?:({set, reset, get}, newValue) => {
       set(productAtom, newValue)
     }
    })
    

    这里要注意一点,selector 如果没有set方法, 那么就是这个只读的RecoilValue类型 ,不可修改,如果有get和set才是RecoilState类型 。

    get方法可以是异步的 ,类似下面这种,可以进行数据请求。

    const productCount = selector({
      key: 'productCount',
      get: aysnc ({get}) => {
          await fetch()
      },
      set?:({set, reset, get}, newValue) => {
       set(productAtom, newValue)
     }
    })
    

    Rocoil状态管理的功能非常强大,我这里只抛一个砖,更多细节请看github 。

    五 利用Enzyme进行单元测试

    利用enzymejs ,可以简单模拟真实用户的行为的去测试组件。 而不是把只能对函数的测试。

    提高了测试的覆盖度。 这里主要讲他的三种渲染方式

    1.浅渲染(shallow)

      describe('initialize', () => {
        const wrapper = shallow(<MultiCheck {...props} />)
        it('renders the label if label provided', () => {
          expect(wrapper.find(".status").text()).toMatch("test-my-label")
        });
    
        it(" test is the columns show correctly", () => {
          expect(wrapper.find(".content").get(0).props.style.width).toEqual(160)
        })
    
      });
    

    wrapper拿到之后就可以各种dom操作了,还可以模拟用户点击,下面代码就先找到一个input,模拟change事件,并发送了一个eventTarget。

      it(" test onChange if click select all", () => {
          let selectAllBtn = wrapper.find(".item").at(0).find("input")
          expect(selectAllBtn).toHaveLength(1)
          selectAllBtn.simulate("change", { target: { checked: true } })
          expect(props.onChange.mock.calls.length).toEqual(1);
       })
    

    2. 完全渲染(mount)

    describe('Home', () => {
        const wrapper = mount(<RecoilRoot><Home /></RecoilRoot>)
        it(" test all checkout status if click select all", () => {
            let selectAllBtn = wrapper.find(".item").at(0).find("input")
            expect(selectAllBtn).toHaveLength(1)
            selectAllBtn.simulate("change", { target: { checked: true } })
            wrapper.find(".item").forEach(ele => {
                expect(ele.find('input').get(0).props.checked).toEqual(true)
            })
        })
    })
    

    什么时候需要深层渲染, 比如,我们的组件是对RecoilRoot有依赖的, 是一个嵌套组件,如果靠浅渲染,是拿不到子组件的Dom结构的。 所以要用mount。

    3 . 静态渲染(render)

    describe('<Foo />', () => {
      it('renders three `.foo-bar`s', () => {
        const wrapper = render(<Foo />);
        expect(wrapper.find('.foo-bar')).to.have.lengthOf(3);
      });
    
      it('renders the title', () => {
        const wrapper = render(<Foo  />);
        expect(wrapper.text()).to.contain('unique');
      });
    });
    

    这种渲染就无法进行事件模拟了,只能对文本进行判断了。

    以上几种渲染,在jtest根据项目实际情况来,灵活搭配可以提高测试效率。

    六 结语

    通过这几个框架的学习,感觉有好处,也有弊端,配合swr和recoil确实能提高开发效率,毕竟redux还是太重了。 但是swr和recoil不能友好的互相结合,selector里面就不能直接用useSwr,这个是一个问题。recoil还是太新。小项目玩一玩可以,大项目上的话还是需要谨慎而为。 好了,告诉大家项目源码地址吧:github.com/tanghui315/…


    起源地下载网 » 2021最新React Hooks+TS项目最佳实践

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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