react结合typescript-ts项目示例_新建ReactHooks+TypeScript项目最佳实践

前言:我写了一个千篇一律的 React 项目。 突然想玩点新东西。 我平时经常使用JS。 但团队合作,TS是最好的方式。 所以这个小项目使用了TS。 结合RecoilJs+Swr组合构建数据处理层。 据说单元测试非常重要,但真正实施的公司却很少。 用 Enzyme+Jtest 测试 React 组件真的很酷。 所以记录一下整个过程。 我们一起来学习吧。

1、关键知识素养

里面提到了几个关键框架。 下面我将简要介绍其中的一些。 具体细节可以去他们的官方GitHub学习。

1.Recoiljsfacebook新推出的reacthooks状态管理框架,比较轻量级,易于使用。 几个优点:灵活共享状态,并保持高性能,根据变化的状态进行高效可靠的估计,Atom操作仅影响可订阅的变量状态,并防止全局重新渲染。 还有跨页面的 Cross-AppObservation 状态传输。

2. swr是一个ReactHooks库,提供远程数据请求,也可以与axios配合使用。 主要特点是:手动中断协程、手动重试请求、避免async、await等句子糖分,且无反弹。 与Reacthook结合使用更方便。

3. Enzyme 是 Airbnb 的 React 开源测试框架。 它的Api和Jquery一样灵活。 由于Enzyme使用cheerio库来解析HTML,而cheerio通常用于用节点爬虫来解析页面,因此在服务器端也称为Jquery。 Enzyme实现了一套类似于Jquery的Api。 其核心代码是创建一个ReactWrapper类或ShallowWrapper来包装React组件。 不同的是ShallowWrapper类只会渲染组件的第一层,而不是自身组件,所以它是一个浅层渲染。 其实它还提供了Mount方法,可以做深度渲染。

2. 建立项目的基本结构

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.jsonreact结合typescript,并安装相关依赖

{
  "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"
  }
}

之后,您可以使用yarninstall

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

目录结构,源码稍后介绍 3. 创建socket请求和mock数据

1、先创建一个mock socket,并在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”返回的数据是一个链表,最小为9,最大为19。

label:/[az]{3,5}/:表示上面的标签值是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(, 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
  extends Pick<
  responseInterface<AxiosResponse, AxiosError>,
  'isValidating' | 'revalidate' | 'error'
  > {
  data: JsonData | undefined
  response: AxiosResponse | undefined
  requestKey: string
}
export interface Config
  extends Omit<
  ConfigInterface<AxiosResponse, AxiosError>,
  'initialData'
  > {
  initialData?: JsonData
}
//useRequest 封装swr的请求hooks函数
export default function useRequest(
  request: GetRequest,
  { initialData, ...config }: Config = {}
): Return {
  //如果是开发模式,这里做环境判断,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,
    AxiosError
  >(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({
    url: "get/options"
 })

data是axios返回的data响应数据。

这里也可以使用,调用requestKey

 const { data: data,requestKey } = useRequest({
    url: "get/options"
 })

requestKey的作用是mutate可以用来自动更新数据但是执行数据变更

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

看来swr还是比较强的。 更多使用请前往他的官方github网站。 这里我就不详细说了。

4. 使用 RecoilJS 进行状态管理

1. 创建状态数据

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

Atom是recoil创建状态的最基本的操作,这里虽然有选择器,但是它的功能比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)
 }
})

这里需要注意的是react结合typescript,如果选择器没有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。

5. 使用 Enzyme 进行单元测试

使用enzymejs,您可以简单地模拟真实用户的行为来测试组件。 而不是只对功能进行测试。

增强测试覆盖率。这里主要说一下他的三种渲染方式

1.浅层渲染(shallow)

  describe('initialize', () => {
    const wrapper = shallow()
    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操作,也可以模拟用户点击。 下面的代码首先找到一个输入,模拟变更风暴,然后发送一个 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.完全渲染(安装)

describe('Home', () => {
    const wrapper = mount()
    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结构。 所以使用挂载。

3.静态渲染(render)

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

这些效果图很难模拟风暴,只能通过文字来判断。

以上效果图,在jtest中可以根据项目的实际情况灵活搭配,提高测试效率。

六点结论

通过对这些框架的学习,感觉有好处,也有隐忧。 配合swr和recoil确实可以提高开发效率,虽然redux还是太重了。 而且,swr和recoil不能友好地组合在一起,useSwr也不能直接用在选择器上。 这是个问题。 反冲力仍然太新。 小项目玩玩还可以,但是大项目还是需要谨慎。 好吧,我告诉你该项目的源代码地址: