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

The above basic construction is completed, others can be added according to the needs of the project