preface
The company needs an taro wechat mini program shelf, manpower is not enough by this dish chicken to build, a novice, welcome big heads to comment
introduce
The project is based on TARO3.x, with TS, DVA, and SCSS, written using React hooks
start
The Taro project is based on Node, please make sure you have a newer Node environment (>=12.0.0)
The installation
Global install @tarojs/cli
npm install -g @tarojs/cli
or
yarn global add @tarojs/cli
Copy the code
Project initialization
taro init myProject
Copy the code
The template source can be Gitee or Github
If you are not familiar with hooks, use the tarol-hooks template
After the installation is complete, you can use the command to see the effect
npm run dev:weapp / / compile
npm run build:weapp / / packaging
Copy the code
The basic structure is there, but for the whole project, there are things we can do for it
dva
Dva is a data flow solution based on Redux and Redux-Saga. In order to simplify the development experience, DVA also has additional built-in React-Router and FETCH, so it can also be understood as a lightweight application framework. (This is the introduction of the official website)
The project uses DVA
After completing the above steps we need to install dependencies in the project
npm i --save dva-core dva-loading redux react-thunk redux-logger
Copy the code
Manually create the file dva.ts in SRC /utils/
// src/utils/dva.ts
import {create } from 'dva-core';
// import {createLogger } from 'redux-logger';
import createLoading from 'dva-loading';
let app: {use: (arg0: any) = > void; model: (arg0: any) = > any; start: () = > void; _store: any; getStore: () = > any; dispatch: any};
let store: {dispatch: any};
let dispatch: any;
let registered: boolean;
function createApp(opt: {models: any[]; initialState: any }) {
// Redux log, referring to redux-logger
// opt.onAction = [createLogger()];
app = create(opt);
app.use(createLoading({}));
if(! registered) opt.models.forEach((model: any) = > app.model(model));
registered = true;
app.start();
store = app._store;
app.getStore = () = > store;
dispatch = store.dispatch;
app.dispatch = dispatch;
return app;
}
export default {
createApp,
getDispatch() {
return app.dispatch;
},
getStore() { // This is a way to get a Store in a non-component file without exposing it
returnapp.getStore(); }};Copy the code
Create the Models folder, the structure under the Models folder
models/index.ts
import global from '@/pages/models/global';
export default [global]; // This is an array. Each item in the array is a separate module
Copy the code
models/global.ts
import Taro from '@tarojs/taro';
import api from '@/services/index'; // This is my wrapper, more on that later
const { login } = api;
export default {
namespace: 'global'.state: {
// User information
userInfo: {},},effects: {*getUserInfo(_, { call }) {
const userInfo = yield call(Taro.login, { lang: 'zh_CH' });
const c = yield call(login, 'GET', { bb: 1 });
console.log(userInfo, c);
},
reducers: {
setState(state, { payload }) {
return{... state, ... payload, }; }},}};Copy the code
Finally, we’ll do it in app.ts
import Taro from '@tarojs/taro'
import React, {Component } from 'react'
/* dva */
import {Provider} from 'react-redux'
import dva from './utils/dva'
import models from './models/index'
// Global style
// import './styles/base.scss'
const dvaApp = dva.createApp( {
initialState: {},
models: models,
} );
const store = dvaApp.getStore();
class App extends Component {
componentDidMount () {
if (process.env.TARO_ENV === 'weapp') {
// Cloud development initialization
// Taro.cloud.init({env:'',traceUser: true,})}}// The render() function in the App class has no real effect
// Do not modify this function
render() {
return (
<Provider store={store} >
{this.props.children }
</Provider>)}}export default App
Copy the code
encapsulation
Create request.ts manually and write a simple request
import Taro from '@tarojs/taro';
import { apiPrefix } from './config';
const HTTP_SUCCESS_CODE = [200.0];
const HTTP_ERR_CODE = {
400: 'Error request'.401: 'Unauthorized request, please log in again'.403: 'Server denied access'.404: 'Request failed, specified resource not found'.405: 'Requested method is disabled'.406: 'Server will not accept this request'.407: 'Request requires proxy authorization'.408: 'Request timed out'.409: 'Server conflicting while completing request'.410: 'Server has permanently deleted requested resource'.411: 'Server will not accept requests without a valid content length header field'.412: 'Server does not meet prerequisites'.413: 'Request entity too large'.414: 'Requested URI too long'.415: 'Unsupported media types'.416: 'Requested scope does not meet requirements'.417: 'Requested header field does not meet server requirements'.500: 'Server internal error'.501: 'Server cannot recognize request method'.502: 'Gateway error'.503: 'Server is currently unavailable'.504: 'Network timed out. Please try again on a network.'.505: 'The HTTP version does not support this request'}; interface requestProps {url: string; data? : object; method? : any; loadingCopy: string; }/** * Network request *@param {*} The url path *@param {*} Method Request type *@param {*} Data request parameter *@param {*} LoadingCopy loading text *@returns* /
const Request = ({ url, method, data = {}, loadingCopy = 'Loading' }: requestProps) = > {
loadingCopy && Taro.showLoading({ title: loadingCopy });
return new Promise((resolve, reject) = > {
const cloneData = JSON.parse(JSON.stringify(data));
Taro.request({
url: `${apiPrefix}${url}`,
method,
data: cloneData,
header: {
'Content-Type': 'application/json',},complete: () = > {
loadingCopy && Taro.hideLoading();
},
}).then(
res= > {
if(res.statusCode ! = =200) return; resolve(res? .data ?? res); },err= > {
const error = JSON.parse(JSON.stringify(err));
const { statusCode } = error;
Taro.showToast({ title: HTTP_ERR_CODE[statusCode] || 'Server error' });
console.error('-- throw exception --', error); reject(error); }); }); };export const checkResponse = (res, msgKey: string = 'msg') = > {
const { status, code } = res || {};
const ret = HTTP_SUCCESS_CODE.includes(status) || HTTP_SUCCESS_CODE.includes(code);
if(! ret) { Taro.showToast({title: res[msgKey] || 'Server error' });
return false;
}
return ret;
};
export default Request;
Copy the code
Create an api.ts for unified management of all apis
export default {
login: "login/user" / / login
};
Copy the code
Finally, create an index.ts that handles both API and request methods as one object, for example:
{ login:request(“login/user”) }
import apiRequest from "@/utils/request";
import api from "./api";
const handleApiFun = (url: string) = > {
return function(method = "GET", data: object, loadingCopy = "Loading") {
return apiRequest({
url,
method,
data,
loadingCopy
});
};
};
const ApiFun: any = {};
for (const key in api) {
ApiFun[key] = handleApiFun(api[key]);
}
export default ApiFun;
Copy the code
Package good, combined with dVA components to use, tune the interface try
The Models layer writes a good method
We use this freely in the hooks template
The simple use of DVA is like this, need to study the usage can go to the official website or read other big guy articles to learn
eslint stylelint commit
Developing project specifications is very important. In order to improve work efficiency, make it easier for later generations to add features and optimize maintenance, the goal is to make sure that every line of code is written as if it was written by the only person, no matter how many people are working on the same project.
eslint
The old rules pretend to depend on
npm i @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-taro eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks -D
Copy the code
“Eslint “: “^6.8.0″,” esLint “: “^6.8.0”, “esLint “: “^6.8.0″,” ESLint “: “^6.8.0
The project root directory creates eslintrc.js and eslintignore, the former is the base configuration of ESLint, the latter is to configure ESLint to ignore the file esLint configuration and directly code
module.exports = {
extends: ['taro/react'].plugins: ['react'.'react-hooks'.'@typescript-eslint/eslint-plugin'.'prettier'].rules: {
'react/jsx-uses-react': 'off'.'react/react-in-jsx-scope': 'off'.'no-unexpected-multiline': 'error'.// Forbid multiple ternary lines
'no-var': 'error'.// Do not use var
'prefer-const': 'error'.// Const is recommended
'no-const-assign': 'error'.// Disallow modifying variables declared as const (no-const-assign)
'object-shorthand': 'error'.// Short for method attribute values
'quote-props': ['error'.'as-needed'].// Only use quotation marks for invalid identifiers
'no-array-constructor': 'error'.// Arrays require literal assignments
'no-new-object': 'error'.// object creates objects using literals
'array-callback-return': 'error'.// enforce in the callback of array methods
'prefer-template': 'error'.// Template strings are recommended
'no-eval': 'error'.// Disallow eval
'no-useless-escape': 'error'.Do not use unnecessary escape characters
'func-style': 'error'.// Use named function expressions instead of function declarations
'prefer-rest-params': 'error'.// It is recommended to use rest parameters instead of parameters
'space-before-function-paren': ['error'.'never'].// Do not allow Spaces or before functions
'space-before-blocks': ['error'.'always'].// Need space before block
'no-param-reassign': 'error'.// Reassigning function arguments is not allowed
'prefer-arrow-callback': 'error'.// The arrow function is recommended
'arrow-spacing': 'error'.// arrow function arrows need space before and after the arrow
'arrow-body-style': ['error'.'always'].// Curly braces are required in the body of the arrow function
'no-confusing-arrow': ['error', { allowParens: true}].// Arrow functions are not allowed to be confused with comparisons
'no-useless-constructor': 'error'.// Do not allow unnecessary constructors
'no-dupe-class-members': 'error'.// Duplicate names are not allowed in class members
'no-duplicate-imports': ['error', { includeExports: true}].// Repeat imports are not allowed
'import/first': 'error'.// Import is placed before all other statements
'dot-notation': 'error'.// Use dot notation to access attributes
'no-restricted-properties': 'error'.// the power operator ** is used when exponentiating
'one-var': ['off'.'always'].// Enforce separate declarations of variables in functions
'no-multi-assign': 'error'.// Do not use continuous variable assignments
'no-plusplus': 'error'.// Do not use the unary increment decrement operator (++, --)
'no-unused-vars': 'off'.// No unused variables are allowed
'no-case-declarations': 'error'.// Lexical declarations are not allowed in case/default clauses
'no-nested-ternary': 'error'.// Ternary expressions should not be nested and are usually single-line expressions
'no-unneeded-ternary': 'error'.// Avoid unnecessary ternary expressions
'no-mixed-operators': 'off'.// Mixing of different operators is not allowed
'nonblock-statement-body-position': ['error'.'beside'].// Enforces the position of a single line statement
'brace-style': 'error'.// Braces are required
'no-else-return': 'error'.// If all if statements return with a return, else is not needed. If the if block contains a return, then the else if block after it also contains a return, then we can split the else if
'keyword-spacing': ['error', { before: true}].// Enforce consistent spacing before and after keywords
'space-infix-ops': ['error', { int32Hint: false}].// Separate the operators with Spaces
'padded-blocks': ['error'.'never'].// Don't intentionally leave unnecessary white space
'array-bracket-spacing': ['error'.'never'].// Do not add Spaces in square brackets
'object-curly-spacing': ['error'.'always'].// Put a space in the curly braces {}
'comma-spacing': ['error', { before: false.after: true}].//, avoid Spaces before, need Spaces after
'key-spacing': ['error', { beforeColon: false}].// There must be Spaces between key values in the attributes of the object
'no-trailing-spaces': 'error'.// No space at the end of the line
'no-multiple-empty-lines': 'error'.// Avoid multiple blank lines. Only one blank line is allowed at the end of the file
'no-new-wrappers': 'error'.// Primitive wrapping instances is not allowed
'new-cap': 'off'.// Require constructor names to start with a capital letter
'default-case': 'error'.// Require a default branch in the switch statement
'jsx-quotes': ['error'.'prefer-double'].// Forces all JSX attribute values that do not contain single quotes to use single quotes
quotes: ['error'.'single'].// string uses single quotation marks.
eqeqeq: ['error'.'always'].// Use === and! == instead of == and! =
radix: ['error'.'as-needed'].// The cardinality argument is required
camelcase: ['error', { properties: 'always'}].// Require camel name for objects, functions, and instances
'prefer-destructuring': [
'error',
{
array: true.object: true}, {enforceForRenamedProperties: false],},// Get and use one or more of the object's property values by destructuring the object's assignment
'spaced-comment': [
'error'.'always',
{
line: {
markers: ['/'].exceptions: [The '-'.'+'],},block: {
markers: ['! '].exceptions: [The '*'].balanced: true,}},],// Enforces consistent whitespace in comments
/ / "text-indent:" [" error ", 2, {" SwitchCase ": 1}], / / forced two Spaces
// 'no-underscore-dangle': 'error', // do not use pre-underscore or post-underscore}};Copy the code
stylelint
StyleLint is “a powerful, modern CSS detection tool” that, like ESLint, helps us avoid errors in style sheets by defining a series of coding style rules.
en…. Put your dependence
npm i stylelint stylelint-config-recess-order stylelint-config-standard stylelint-order -D
Copy the code
The project root directory creates stylelintrc.js, which is the base configuration of Stylelint, and stylelintignore, which configures stylelint to ignore a file
module.exports = {
processors: [].plugins: [].extends: ['stylelint-config-standard'.'stylelint-config-recess-order'].// This is the official recommended way
rules: {
'unit-no-unknown': [true, { ignoreUnits: ['rpx']}],'no-descending-specificity': null.'no-duplicate-selectors': null,}};Copy the code
commit
Prior to code submission, code rule checking ensures that all code entering git repositories complies with code rules. However, lint can be slow to run on an entire project, and Lint-staged versions can be fast because they only detect files in staging.
npm i husky lint-staged -D
Copy the code
Configure in package.json
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix"."prettier --write"."git add"]."*.scss": [
"stylelint --fix"]},"husky": {
"hooks": {
"pre-commit": "lint-staged"."commit-msg": "commitlint -E HUSKY_GIT_PARAMS"}}Copy the code
Git COMMIT trigger the pre-commit hook, run lint-staged commands, and execute eslint commands on *.ts. Eslint should be configured in advance.
Commitlint specification
website
npm install --save-dev @commitlint/config-conventional @commitlint/cli
Copy the code
Generate the configuration file commitlint.config.js
const types = [
'build'.The main purpose is to modify the submission of the project build system (e.g. glUP, Webpack, rollup configuration, etc.)
'ci'.// Modify the submission of the continuous integration process (Kenkins, Travis, etc.) for the project
'chore'.// Changes to the build process or helper tools
'docs'.// Document submission
'feat'.// add a new feature
'fix'./ / fix the bug
'pref'.// Commits related to performance and experience
'refactor'.// Code refactoring
'revert'.// Roll back some earlier commit
'style'.// Code changes that do not affect the program logic, mainly style optimization, modification
'test'.// Test related development,
],
typeEnum = {
rules: {
'type-enum': [2.'always', types],
},
value: () = > {
returntypes; }};module.exports = {
extends: ['@commitlint/config-conventional'].rules: {
'type-enum': typeEnum.rules['type-enum'].'subject-full-stop': [0.'never'].'subject-case': [0.'never'],}};Copy the code