前言:我写了一个千篇一律的 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也不能直接用在选择器上。 这是个问题。 反冲力仍然太新。 小项目玩玩还可以,但是大项目还是需要谨慎。 好吧,我告诉你该项目的源代码地址:
发表评论