前言
⭐ Nextjs-TS-Antd-Redux-Storybook-Jest-Starter
之所以有该我的项目呢,是因为日常可能本人须要练手其余 Next.js 我的项目,又不想每次都重新配置一遍,但基于强迫症失常企业级我的项目该有的配置感觉不能少了,于是就想开搞一个通用脚手架模板。
说起 Next.js,8 月份写了一篇文章手把手带你入门 NextJs(v9.5),次要是因为网上大部分 Next.js 是旧版本 v7.x 的教程,于是写个较新的 9.5 版,没想到 10 月就出了 Next.js 10,措手不及,不过更新局部次要是图片优化等,能够照样看。
该我的项目也是想把日常工作中我感觉实际比拟好的点加进来,也打算依据该我的项目继续跟进良好标准和最新库版本。当然,到具体业务场景的话脚手架必定多少须要改,但指标心愿能升高批改的老本,起码根本配置得搞好。
该脚手架次要库和版本:
Next.js 10.x
React 17.x
TypeScript 4.x
Ant Design 4.x
Styled-components 5.x
Storybook 6.x
初始化 Next.js 模板
npx create-next-app nextjs-ts-redux-antd-starter
增加 TypeScript 反对
根目录下新建 tsconfig.json
文件,此时运行yarn dev
,会看到它提醒咱们装置类型库
yarn add --dev typescript @types/react @types/node
顺便把 @types/react-dom
也装上
装置之后,再运行 yarn dev
, 会在根目录主动生成next-env.d.ts
文件,且 tsconfig.json
有了默认配置,这里我再对配置稍加改变。
具体能够参考 TS 官网看配置项:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["src/*"]
},
"target": "es5",
"module": "esnext",
"strict": true,
"allowJs": true, // 容许编译 js 文件
"jsx": "preserve", // 在 .tsx 文件里反对 JSX
"noEmit": true, // 不输入文件
"lib": [
"dom",
"dom.iterable",
"esnext"
], // TS 须要援用的库,即申明文件
"esModuleInterop": true, // 容许 export= 导出,由 import from 导入
"moduleResolution": "node", // 模块解析策略,ts 默认用 node 的解析策略,即绝对的形式导入
"allowSyntheticDefaultImports": true, // 容许从没有设置默认导出的模块中默认导入
"isolatedModules": true, // 将每个文件作为独自的模块
"resolveJsonModule": true, // 容许把 json 文件当做模块进行解析
"skipLibCheck": true, // 跳过所有申明文件的类型查看
"forceConsistentCasingInFileNames": true // 不容许对同一文件应用不统一大小写的援用
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules",
".next",
"dist"
]
}
而后革除洁净目录,把 styles
, pages
只留下一个 index.js
即可,并将 index.js
重命名为index.tsx
import {NextPage} from 'next'
const Home: NextPage = () => {return <div>Hello nextjs-ts-redux-antd-starter</div>}
export default Home
EditorConfig
作为我的项目代码格调的对立标准,咱们须要借助第三方工具来强制
.editorconfig
是跨编辑器保护统一编码格调的配置文件,在 VSCode 中须要装置相应插件 EditorConfig for VS Code,装置结束之后,能够通过输出 Generate .editorcofig
即可疾速生成 .editorconfig
文件,也能够本人新建文件。
在 .editorcofig
文件,就能够大家依据不同来设置文件了,比方我的是这样:
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
Prettier
yarn add prettier -D
同样也须要装置 VSCode 插件Prettier - Code formatter
新建文件.prettierrc
{
"singleQuote": true,
"tabWidth": 2,
"endOfLine": "lf",
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "avoid",
"semi": false,
"bracketSpacing": true,
"overrides": [
{
"files": ".prettierrc",
"options": {"parser": "json"}
}
]
}
再增加疏忽文件.prettierignore
**/*.png
**/*.svg
**/*.ico
package.json
lib/
es/
dist/
.next/
coverage/
LICENSE
yarn.lock
yarn-error.log
*.sh
.gitignore
.npmignore
.prettierignore
.DS_Store
.editorconfig
.eslintignore
**/*.yml
ESLint
yarn add eslint -D
装置完后运行 npx eslint --init
,运行后有选项,抉择如下(自行依据须要):
- To check syntax, find problems, and enforce code style
- JavaScript modules (import/export)
- React
- TypeScript Yes
- Browser Node
- Use a popular style guide
- Airbnb: https://github.com/airbnb/jav…
- JavaScript
- Would you like to install them now with npm (Yes)
npm 装置后会呈现package-lock.json
,如果你默认想用yarn.lock
,为了防止抵触就删掉它。
装置主动生成 .eslintrc
文件,还没完,为了写进去的代码更好更合乎社区标准,咱们再加一些不错的 eslint 插件
eslint-plugin-unicorn
:提供了循环依赖检测,文件名大小写格调束缚等十分实用的规定汇合。eslint-config-prettier
:eslint 和 prettier 混合应用时候,须要批改规定,以避免反复或抵触;该插件即为解决此问题的存在,能够应用它敞开所有可能引起抵触的规定。eslint-plugin-import
:可能正确解析.tsx, .ts, .js, .json
后缀名(还需指定容许的后缀名,增加到 setttings 字段)eslint-import-resolver-alias
: eslint 能辨认 alias 别名自定义门路eslint-import-resolver-typescript
:让eslint-plugin-import
可能正确解析tsconfig.json
中的paths
映射,须要装置它。
我的配置如下,rules
疏忽规定本人增加因人而异
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'airbnb',
'plugin:react/recommended',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended',
'prettier/react',
],
settings: {
'import/resolver': {
node: {extensions: ['.tsx', '.ts', '.js', '.json'],
},
alias: [['src', './src']],
},
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {jsx: true,},
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint', 'react-hooks', 'unicorn'],
rules: {
semi: 0,
indent: 0,
'react/jsx-filename-extension': 0,
'react/prop-types': 0,
'react/jsx-props-no-spreading': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/no-static-element-interactions': 0,
'jsx-a11y/no-noninteractive-element-interactions': 0,
'no-use-before-define': 0,
'no-unused-vars': 0,
'implicit-arrow-linebreak': 0,
'consistent-return': 0,
'arrow-parens': 0,
'object-curly-newline': 0,
'operator-linebreak': 0,
'import/no-extraneous-dependencies': 0,
'import/extensions': 0,
'import/no-unresolved': 0,
'import/prefer-default-export': 0,
},
}
新建文件.eslintignore
,疏忽一些文件的查看
/node_modules
/public
/dist
/.next
/coverage
StyleLint
sass/less/css
eslint-config-prettier
: 利用插件禁用与 Prettier 起抵触的规定stylelint-config-rational-order
: 对关联属性进行分组和排序stylelint-declaration-block-no-ignored-properties
: 矛盾款式疏忽stylelint-order
:强制你依照某个程序编写 css
.stylelintrc
{
extends: [
'stylelint-config-standard',
'stylelint-config-rational-order',
'stylelint-config-prettier',
],
plugins: ['stylelint-order', 'stylelint-declaration-block-no-ignored-properties'],
}
styled-components
以上是应用 sass 或 less 能够齐全照搬配置的,至于该脚手架我决定采纳的 CSS 计划为 styled-components
,stylelint 配置 styled-components 目前无关库尚未实现主动修复,所以--fix
目前是有效的,且须要装置另外的 stylelint 规定插件
yarn add styled-components
yarn add -D @types/styled-components stylelint-processor-styled-components stylelint-config-styled-components
.stylelintrc
{
"processors": ["stylelint-processor-styled-components"],
"plugins": ["stylelint-order"],
"extends": [
"stylelint-config-standard",
"stylelint-config-styled-components"
]
}
再新建文件babel.config.js
{"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true}]]
}
你能够分development
,test
,production
, 对 styled-components 进行辨别设置,比方 babel.config.js
新建文件pages/_document.tsx
,来自定义 Document 的形式来改写代码。它只有在服务器端渲染的时候才会被调用,次要用来批改服务器端渲染的文档内容,个别用来配合第三方 css-in-js 计划应用。
import React from 'react'
import Document, {DocumentContext} from 'next/document'
import {ServerStyleSheet} from 'styled-components'
class MyDocument extends Document {static async getInitialProps(ctx: DocumentContext) {const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {const initialProps = await Document.getInitialProps(ctx)
ctx.renderPage = () =>
originalRenderPage({enhanceApp: App => props => sheet.collectStyles(<App {...props} />),
})
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {sheet.seal()
}
}
}
export default MyDocument
.vscode
在根目录下新建文件夹.vscode
,在该文件夹下新建文件 settings.json, 该文件的配置优先于你本人 VSCode 全局的配置,不会因为团队不同成员的 VSCode 全局配置不同而导致格式化不同。
settings.json
{
"search.exclude": {
"**/node_modules": true,
"dist": true,
".next": true,
"yarn.lock": true
},
"editor.formatOnSave": true,
"editor.tabSize": 2,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
}
}
husky 与 lint-staged
每次提交代码都要对代码先进行 lint 和格式化,确保代码格调对立。于是咱们装置 husky
来解决这个事件,可咱们想每次 lint 格式化的时候,只解决咱们批改的代码(暂存区),能够抉择lint-staged
yarn add -D husky lint-staged
在 package.json
配置 git commit 钩子操作:
"husky": {
"hooks": {
"commit-msg": "commitlint --config .commitlintrc.js -E HUSKY_GIT_PARAMS",
"pre-commit": "lint-staged && yarn tsc"
}
},
"lint-staged": {"*.{tsx,ts,js,jsx}": [
"stylelint",
"prettier --write",
"eslint --fix"
],
"*.{css,less,scss}": [
"stylelint",
"prettier --write"
],
"*.{md,json,yaml,yml}": ["prettier --write"]
},
prettier --write
中的 --write
示意将格式化后的代码写到源文件,不加的话会输入文件。
下面 "pre-commit": "lint-staged && yarn tsc"
我还加了yarn tsc
,ts 查看类型有问题,那当然不给你提交,及早发现错误。
另外需不需要强制 --fix
看集体,因为有人会顾虑强制的话相当于黑盒,你不晓得它对你代码做了什么。
commitlint
咱们提交的前文件曾经会主动格式化了,接下来要搞搞 commit 提交标准问题。
yarn add @commitlint/cli @commitlint/config-conventional -D
默认类型 git commit 类型有如下几种,这是 angular 格调的 commitlint 配置,我本人平时习惯这一套规定。
[
'build',
'ci',
'chore',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test'
];
在根目录新建.commitlintrc.js
:
module.exports = {extends: ['@commitlint/config-conventional'],
}
那如果团队刚来的人没用过也记不住下面那些结尾单词怎么办,于是咱们弄个命令能够让他本人抉择,装置插件
cz-conventional-changelog
:是一个适配器,一个合乎 Angular 团队标准的 preset
yarn add cz-conventional-changelog -D
在 package.json
中配置
{
"scripts": {"commit": "git-cz"},
"config": {
"commitizen": {"path": "node_modules/cz-conventional-changelog"}
}
}
运行yarn commit
,即呈现该页面,供咱们抉择
Redux
根本我的项目标准配置就差不多了,接下来是做我的项目的状态管理工具,我这里抉择了最经典的 Redux,异步解决抉择redux-saga
yarn add redux react-redux redux-saga
yarn add -D @types/react-redux @types/redux-saga redux-devtools-extension next-redux-wrapper
社区还有其余 redux 简化计划,比方应用 redux-actions, 但该我的项目保护仿佛呈现艰难,就不退出应用了;还有 dva 等等或者采纳其余状态治理库例如 mobx,各位能够自行思考替换,这里只是给个罕用计划。
src/redux/index.ts
import {createWrapper, MakeStore} from 'next-redux-wrapper'
import {applyMiddleware, createStore} from 'redux'
import createSagaMiddleware from 'redux-saga'
import {composeWithDevTools} from 'redux-devtools-extension/developmentOnly'
import rootReducer from 'src/redux/rootReducers'
import rootSaga from 'src/redux/rootSagas'
const makeStore: MakeStore<Store.RootState> = () => {const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))
sagaMiddleware.run(rootSaga)
return store
}
export const wrapper = createWrapper < Store.RootState > makeStore
再新建文件 pages/_app.tsx
引入 redux,笼罩 Next.js 默认的 App
import React, {FC} from 'react'
import {AppProps} from 'next/app'
import {wrapper} from 'src/redux'
import Layout from 'src/components/Layout'
const WrappedApp: FC<AppProps> = ({Component, pageProps}) => (
<Layout>
<Component {...pageProps} />
</Layout>
)
export default wrapper.withRedux(WrappedApp)
其余代码及例子演示请看源代码
redux 的我的项目构造有几种,哪种好视乎我的项目大小和复杂程度抉择,该脚手架只是展现一种,按模块来划分 redux 数据结构,并不是说此种形式有多好,具体还是根据我的项目来调整目录构造。
我做个小 demo 是 saga 申请用户数据,返回并展现在页面上,对于 redux State
的类型定义,我放在了根目录 types
文件夹里。
当然这也只是一种参照形式,也能够在 redux 目录模块里新建 types
文件搁置 State
类型定义。
Ant Desgin 反对
yarn add antd
yarn add -D babel-plugin-import
babel.config.js
module.exports = {
plugins: [
[
'import',
{
libraryName: 'antd',
libraryDirectory: 'lib',
style: 'index.css',
},
],
],
}
在 src/_app.tsx
中引入 antd 款式:
import 'antd/dist/antd.css'
Travis 自动化部署
默认先这样设置了(前面加了 Jest 后再退出脚本yarn test
)。不解释,不懂的看我这篇文章 手把手带你入门 Travis 自动化部署
language: node_js
node_js:
- "stable"
cache: yarn
install:
- yarn
script:
- yarn build
Storybook 搭建组件文档
Storybook 是在开发模式下与应用程序一起运行的. 它能够帮忙您构建 UI 组件, 并与应用程序的业务逻辑和上下文隔离开来
npx -p @storybook/cli sb init
装置结束,运行即开启
yarn storybook
而后会有初始一些组件例子,看看就能够删了。
How to write stories 通过给组件写 stories,能够让咱们对整个我的项目用到的组件有大抵理解,比方长什么样等等,还有包含是否 UI 扭转,上面会写。
国际化
yarn add react-i18next i18next
当然也能够间接应用 next-i18next
src/i18n/index.ts
import i18n from 'i18next'
import {initReactI18next} from 'react-i18next'
import zhCN from './locales/zh_CN.json'
import enUS from './locales/en_US.json'
i18n.use(initReactI18next).init({
lng: 'zh_CN',
fallbackLng: 'zh_CN',
resources: {
zh_CN: {translation: zhCN,},
en_US: {translation: enUS,},
},
debug: false,
interpolation: {escapeValue: false,},
})
export default i18n
具体还是间接看代码了,这里就介绍这么多;而后就能够切换语言,把我的项目用到的一些词语句子都集中到 zh_CN.json
和en_US.json
等等写。
Jest 单元测试
为了代码的健壮性,当然是退出单元测试。如果不懂单元测试,请先看我这篇 一文带你理解 Jest 单元测试
yarn add -D jest @types/jest eslint-plugin-jest babel-jest @storybook/addon-storyshots
配置.eslintrc.js
module.exports = {extends: ['plugin:jest/recommended'],
plugins: ['jest'],
}
在根目录下新建文件jest.config.js
module.exports = {moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/node_modules/', '<rootDir>/.next/'],
moduleNameMapper: {'^src(.*)$': '<rootDir>/src$1',
'^server(.*)$': '<rootDir>/server$1',
'^pages(.*)$': '<rootDir>/pages$1',
},
collectCoverageFrom: ['./{src,server}/**/*.{ts,tsx,js,jsx}',
'!**/node_modules/**',
'!**/dist/**',
'!**/coverage/**',
'!**/*.stories.{ts,tsx,js,jsx}',
'!**/{config,constants,styles,types,__fixtures__}/**',
],
watchPathIgnorePatterns: ['dist'],
}
Storybook 和 Jest 的 Snapshots 联合
Jest 能够生成快照测试(Snapshot),通过 snapshot 变动给咱们判断页面元素是否异样,缺失或减少或配置文件是否更改等等。下面装置了 storybook,如果是 react 组件的 snapshot,须要借助其余插件,这里咱们转为依附 storybook 的 stories 生成。
针对 react,Jest 将为虚构 DOM 拍摄快照,将其转化为 json 数据,在下一次运行时比对两张快照是否有偏差。
yarn add -D @storybook/addon-storyshots
在根目录新建jest.config.js
, 针对 snapshot 的配置如下,其它配置按我的项目配置了,参考 jest.config.js
module.exports = {
transform: {'^.+\\.stories\\.[tj]sx?$': '@storybook/addon-storyshots/injectFileName',
'^.+\\.[tj]sx?$': 'babel-jest',
},
}
新建文件src/__tests__/storyshot.test.ts
import initStoryshots, {multiSnapshotWithOptions} from '@storybook/addon-storyshots'
initStoryshots({test: multiSnapshotWithOptions(),
})
之后组件中有写 stories
的中央,应用 yarn jest
,除了运行测试,也会主动为*.stories.tsx
比对 / 生成 snapshot。
对于生成的 snapshot 你会看到
比方我写了 Footer
组件的,只有 HTML 标签和对应属性,这样检测还不够,因为不晓得 css 类的属性做了什么扭转,因为我用的 css 计划是styled-components
,所以须要再进行配置:
yarn add -D jest-specific-snapshot jest-styled-components
配置src/__tests__/storyshot.test.ts
import initStoryshots, {multiSnapshotWithOptions} from '@storybook/addon-storyshots'
import 'jest-styled-components'
import {addSerializer} from 'jest-specific-snapshot'
import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer'
addSerializer(styleSheetSerializer)
initStoryshots({test: multiSnapshotWithOptions(),
})
再来看当初的 snapshot,曾经有了一堆款式能够参考比照了,这样轻微组件款式批改都能够被捕捉到了;
Enzyme
Jest 能够对函数,类等等有短缺的 API 来测试,但对于 React 组件,想具体进行测试,则须要装置其余插件来反对,如 react-test-library
和enzyme
,这里我就选我用过绝对多一点的 enzyme(出自 Airbnb 公司),同时须要装置它的适配器。
这里因为 React 曾经降级到 17 版本了,然而 enzyme 官网适配器还没有降级到对应 17 版本的,有些测试方法可能会报错,所以临时应用目前 Github 应用较多的代替版本的这个库@wojtekmaj/enzyme-adapter-react-17
,等官网更新了再替换。这个只是供测试用,不会影响到线上环境,只有 enzyme 自带所有办法能按预期运行不报错就行,这样就能好好写咱们的测试用例了。
yarn add enzyme @wojtekmaj/enzyme-adapter-react-17 -D
在根目录新建文件jest.setup.ts
import Enzyme from 'enzyme'
import Adapter from '@wojtekmaj/enzyme-adapter-react-17'
Enzyme.configure({adapter: new Adapter() })
同时在 jest.config.js
中导入
module.exports = {setupFiles: ['<rootDir>/jest.setup.ts'],
}
通常状况,测试 React 组件是意义不大的,比拟须要测试的就是比方 UI 组件,用得较多的通用组件,还有一些组件如一改变全身的容易有 bug 行为的来针对测试。
生成 CHANGELOG 和自动化版本治理
这里我应用standard-version
。
yarn add -D standard-version
standard-version 是一款遵循语义化版本(semver)和 commit message 标准规范 的版本和 changlog 自动化工具。
"bump-version": "standard-version --skip.commit --skip.tag"
运行 yarn bump-version
后,会发现 package.json 的版本号变了(前提你有了 feat 或 fix 等等 commit 的改变),还有主动生成 CAHNGEALOG.md,这些都能够自定义配置
Github 打 tag 版本
点击 Github 我的项目页面左边创立 release
而后填入版本号,详细信息我把 CHANGELOG.md 的内容间接搬过去,而后按 Publish release
就能够了。
欠缺 script 命令
在 package.json
中
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"commit": "git-cz",
"test": "jest",
"coverage": "yarn jest --coverage",
"lint": "yarn lint:eslint && yarn lint:css",
"lint:eslint": "eslint --ext js,jsx,ts,tsx .",
"lint:css": "stylelint **/*.{ts,tsx}",
"prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"","tsc:client":"tsc --noEmit -p tsconfig.json","storybook":"start-storybook -p 6006","build-storybook":"build-storybook -o ./dist_storybook","bump-version":"standard-version --skip.commit --skip.tag"
},
LICENSE
增加个开源协定,我抉择宽松的 MIT 协定
MIT License
Copyright (c) 2020 nextjs-ts-redux-antd-starter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
运行我的项目
如上演示还略过一些细节其余配置,须要具体的就看源码吧。
整个脚手架我是不打算退出太多货色的,如下图所示,毕竟做为模板脚手架,加太多功能反而要用的时候要删除一大堆麻烦,因为做的不是某类型业务网站,有一些只能尽量有个 Demo 参考就行。所以我会尽量放弃简洁,之后保护我会偏向于 Next.js 配置和前端工程化及性能优化的角度进行欠缺,而后就是一些通用的函数和性能。
结语
脚手架到这里就完了?还没有,还有很多没加,比方整顿 Next.js 的 config 配置,优化 SEO,公布到线上网站和 npm,一些兼容,非凡页面解决,响应式等等。
当然,在我写这篇文章时的脚手架多少也有写的不好或不欠缺的中央,因为刚起步,所以该脚手架会继续保护,把工作实际和学习到的最佳实际运行到该我的项目里,一直放弃更新,敬请关注,欢送 star ???????????? https://github.com/Jacky-Summer/nextjs-ts-antd-redux-storybook-starter
-
ps:
- 集体技术博文 Github 仓库
- monki-ui:基于 React + TypeScript + Dumi + Jest + Enzyme 开发 UI 组件库,将来会写相干文章
感觉不错的话赏个 star,给我继续创作的能源吧!下次持续 …