Предисловие: Написал шаблонный проект React. Вдруг захочется поиграть во что-нибудь новенькое. Обычно чаще используют JS. Но командная работа, ТС - лучший способ. Итак, этот небольшой проект использует TS. Затем объедините RecoilJs + Swr, чтобы создать слой обработки данных. Говорят, что модульное тестирование важно, но немногие компании на самом деле его реализуют. Очень здорово использовать Enzyme+Jtest для тестирования реагирующих компонентов. Так документируйте весь процесс. Давайте учиться вместе.
1. Грамотность ключевых знаний
Выше упомянуто несколько ключевых фреймворков. Я кратко представлю некоторые из них ниже. Чтобы узнать подробности, вы можете перейти на их официальный GitHub, чтобы узнать о них.
1.RecoiljsНовая структура управления состоянием Facebook для реагирующих хуков относительно легка и проста в использовании. Несколько преимуществ: гибкое совместное использование состояния, поддержание высокой производительности, эффективное и надежное выполнение вычислений в соответствии с изменяющимся состоянием, операции Atom влияют только на состояние подписанной переменной и позволяют избежать глобального повторного рендеринга. Существует также Cross-App Observation для передачи состояния между страницами.
2. SwrЭто библиотека реактивных крюков, которая обеспечивает удаленные запросы данных, и он также может использоваться в сочетании с Axios. Основными функциями являются: автоматический промежуточный опрос, автоматическая повторение запросов, избегайте записи синтаксических сахаров, таких как Async и ждут, и никаких обратных вызовов. Легче использовать в сочетании с реактивными крючками.
3. EnzymeЭто среда тестирования с открытым исходным кодом для React от Airbnb.Его API такой же гибкий, как Jquery, потому что Enzyme использует библиотеку cheerio для анализа html, cheerio часто используется для сканера узлов для анализа страниц, поэтому его также называют серверным Jquery. Enzyme реализует набор API-интерфейсов, похожих на Jquery. Его основной код заключается в создании класса ReactWrapper или ShallowWrapper для обертывания компонентов React. Разница в том, что класс ShallowWrapper отображает только первый слой компонента, а не собственный компонент, поэтому он является разновидностью поверхностного рендеринга. Конечно, он также предоставляет метод Mount, который может выполнять глубокий рендеринг.
2. Создайте базовую структуру проекта
1. Быстро создайте базовую конфигурацию веб-пакета
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"
}
}
Затем при установке пряжи вы можете
3. Создайте структуру каталогов проекта и основные страницы
3. Создавайте запросы к интерфейсу и имитируйте данные
1. Сначала создайте фиктивный интерфейс и создайте Index.ts в папке Mock.
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:/[a-z]{3,5}/ : это означает, что значение метки внутри состоит из 3-5 букв и генерируется случайным образом.
value|+1:99: означает, что значение value увеличивается с 99
Mockjs очень мощный и может создавать богатые типы данных и структуры.Вы можете перейти на официальный сайт для подробного использования.
Затем введите этот макет в файл записи entry.tsx, чтобы он работал.
//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 на самом деле очень проста: сделать запрос реакции в компоненте ловушек. он может использовать
const { data: data } = useRequest<Repository[]>({
url: "get/options"
})
data — это данные, возвращаемые данными ответа axios.
Его также можно использовать здесь, вызовите 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, чтобы понять. Я не буду здесь вдаваться в подробности.
4. Используйте RecoilJS для управления состоянием
1. Создайте данные о состоянии
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: []
})
Атом — это самая основная операция отдачи для создания состояния, на самом деле есть еще и селектор, который немного мощнее атома.
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)
}
})
Здесь следует отметить, что если у селектора нет метода 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
Используя энзимы, вы можете просто имитировать поведение реальных пользователей для тестирования компонентов. Вместо того, чтобы ставить только тесты на функции.
Улучшенное тестовое покрытие. Здесь мы в основном говорим о трех его методах рендеринга.
1. Поверхностный рендеринг
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)
})
});
Получив его, оболочка может выполнять различные операции с 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(<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-структуру дочерних компонентов. Так что используйте крепление.
3. Статический рендеринг (рендер)
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 title="unique" />);
expect(wrapper.text()).to.contain('unique');
});
});
Этот тип рендеринга не может выполнять симуляцию событий и может только оценивать текст.
Приведенные выше визуализации в jtest в соответствии с реальной ситуацией в проекте, гибкое словосочетание может повысить эффективность тестирования.
Шесть выводов
Изучая эти фреймворки, я чувствую, что есть преимущества и недостатки.Сотрудничество с swr и recoil действительно может повысить эффективность разработки.В конце концов, редукс все еще слишком тяжелый. Однако swr и recoil не могут сочетаться друг с другом по-дружески, а useSwr нельзя использовать непосредственно в селекторе — это проблема. Отдача еще слишком новая. Вы можете играть с небольшими проектами, но вам все равно нужно быть осторожным, когда дело доходит до больших проектов. Хорошо, позвольте мне сказать вам адрес исходного кода проекта:GitHub.com/tangui315/…