Recently, I have been working on the requirements of H5 official account. The technology stack used is like the title. From the initial project to the stable growth stage, React is used at the front end. If you need to use React, please refer to wechat H5.

Features of this paper:

  • React+H5+ wechat
  • Switch from Vue to React.

Project Introduction:

The car logistics platform can complete the order of car transportation online through the public account.

  • Line price enquiry
  • Order information filling
  • Address book management
  • Real-name authentication
  • Bank card binding
  • coupons
  • WeChat pay
  • Activity posters

The body of the

  1. Distinguish Umi from Dva
  2. Umi configuration
  3. React is usually written like this
  4. Introduction to Dva use
  5. WeChat configuration
  6. TypeScript components
  7. H5 tip
  8. logging
  9. other

1. Differentiate BETWEEN Umi and Dva

When I first changed from Vue to React, it was easy to confuse Umi and Dva. They both called themselves application frameworks.

Umi

Loosely speaking: this can be interpreted as a vue-CLI scaffolding that helps you generate project templates with the Router right out of the box. Integration of Router + ANTD, as well as some internationalization, configuration routing and other functions of deep integration, Umi documents.

Dva

Loosely speaking: can be understood as a Vuex – like state management library. Integrated redux + ReduxSaga, Dva documentation.

Why Umi + Dva

When developing with Vue, we prefer vuE-CLI template creation and Vuex state management.

Umi is also more powerful and closer to business scenarios than create-React-app, the official React scaffolding, right out of the box.

React had Redux for state management, but redux-Saga was redux-Saga, which was packaged by Dva as a more useful data flow solution. See “An article Summarizing Redux, React-Redux, redux-Saga”.


2. Umi configuration

Umi has a powerful configuration. The following lists the important configurations used for Umi. For details, see configuration on the official website.

The environment variable

The front end packages different environment code based on environment variables, such as Api addresses, static resource addresses, and so on.

branch Packaging scripts The environment Address of the Api Static resource address
Maser build:test test fat.*.com cdn.fat.*.com
Develop build:prod A formal *.com cdn.*.com
// package.json
{
    "scripts": {
      "start": "cross-env APP_TYPE=site BUILD_ENV=dev PORT=80 umi dev"."build": "umi build"."build:test": "BUILD_ENV=test umi build"."build:prod": "BUILD_ENV=prod umi build",}}Copy the code

Umi – plugin – react configuration

React plugin is an official plugin for React. It loads pages on demand and rem ADAPTS.

  • dynamicImportSpecify the loading component to enter the page.
  • hdEnable rem scheme

Wechat development must turn off the PWA option, otherwise it will lead to serious cache after the launch.

Other configuration

PostCSS/ load on demand/Theme/Proxy Proxy.

code

import pageRoutes from './router.config';
import theme from '.. /src/theme';
import webpackPlugin from './plugin.config';

const plugins = [
  [
    'umi-plugin-react',
    {
      antd: true.dva: {
        hmr: true,},dynamicImport: {  // 
        loadingComponent: './components/PageLoading/index'.webpackChunkName: true,},pwa: false.title: {
        defaultTitle: 'Default title',},dll: false.hd: true.fastClick: false.routes: {
        exclude: []},hardSource: false,}]];const env = process.env.BUILD_ENV,
  publicPath = {
    "dev": ""."test": "//*.fat.*.com/"."prod": "//*.*.com/"
  }[env];

const apiPath = {
    "dev":  'http://*.feature.*.com/api'."test": 'http://*.fat.*.com/api'."prod": 'https://*.*.com/api'
  }[env];


export default {
  base: '/'.publicPath: publicPath,
  define: {
    APP_TYPE: process.env.APP_TYPE || ' '.apiPath: apiPath || ' ',},// history: 'hash', // default is browser
  plugins,
  routes: pageRoutes,
  theme: { / / theme
    'brand-primary': theme.primaryColor,
    'brand-primary-tap': theme.brandPrimaryTap,
  },
  externals: {},
  lessLoaderOptions: {
    javascriptEnabled: true,},targets: {
    android: 5.chrome: 58.edge: 13.firefox: 45.ie: 9.ios: 7.safari: 10,},outputPath: './dist'.hash: true.alias: {},
  proxy: { / / agent
    '/api/': {
        changeOrigin: true.target: 'http://doclever.xin.com/mock/5d0b67ac3eb3ea0008d58a31',}},ignoreMomentLocale: true.manifest: {
    basePath: '/',},chainWebpack: webpackPlugin,
  extraPostCSSPlugins: [ / / postcss plug-in
    require('postcss-flexbugs-fixes')],es5ImcompatibleVersions: true.extraBabelPlugins: [['import', { libraryName: 'antd-mobile'.style: true }]  // Load the ANTD-mobile style file on demand]};Copy the code

3. How do you React

React/Vue React/Vue React/Vue React/Vue React/Vue React/Vue React

Map replaces the V-for instruction

const arr = ['aaa'.'bbb'.'ccc'];
arr.map(str= > <div>{str}</div>);
Copy the code

Logical operation symbol replaced with V-if/V-else

const show = false;
render (){
	return <>
		{show && <div>I've shown</div>}
        {show ? <div>To true to show</div> : <div>To false to show</div>}
    </>
};
Copy the code

Route parameters are transmitted across pages

In React, only components wrapped with withRouter can obtain routing parameters. In BasicLayout, page-level components must be wrapped manually if they need to obtain routing parameters.

import { withRouter } from 'react-router-dom'
class FixBar extends React.Component{
    public render() {
        const { location: { query = {} }, } = this.props;
        return (<div>{query. Id | | 'default text'}</div>); }}export default withRouter(FixBarCom);

Copy the code

Listen for props/state changes

In Vue, watchapi can be used to monitor parameter changes conveniently. React requires other apis to implement similar functions. GetDerivedStateFromProps is not used often, that is, when the value of state is dependent on props at all times.

componentDidUpdate(prevProps, prevState) {
  / / to monitor props
  if (this.props.userID ! == prevProps.userID) {// doSomething
  }
  / / to monitor the state
  if (this.state.name ! == prevState.name) {// doSomething}}Copy the code

SetState and fiber

For better performance, React uses a Fiber architecture, which means that setState operations can be asynchronous.

/ / do not recommend
this.setState({ a:1})
this.setState({ a:this.state.a + 1})
/ / recommend
this.setState({a:1},() = > {
   this.setState({ a:this.state.a + 1})})/ / do not recommend
this.setState({
  counter: this.state.counter + this.props.increment,
});
/ / recommend
this.setState((state, props) = > ({
  counter: state.counter + props.increment
}));
Copy the code

ES6 with setState:

const data = { a: '11' }
this.setState({ ... data })this.setState({ a:'222'})
this.setState({ ['a'] :'333' })
this.setState((prevState, props) = > ({a: '444' }));
Copy the code

The React debounce stabilization

Many scenarios require additional shock protection for the onChage event Input, using the lodash. Debounce method.

import _ from 'lodash';
class DebounceExample extends React.Component {
  constructor(props) {
    this.handleInputThrottled = _.debounce(this.getSomeFn, 100)}getSomeFn(res){
    // doSomeThing
  }
  render() {
    return <input onChange={this.handleInputThrottled}/>}}export defaultDebounceExample;
Copy the code

React createPortal

Some scenarios require that the content node of the child element be placed in another component, such as the box component, which wants to be under the root of the body element each time, using createPortal.

Official explanation:

Portal provides an excellent solution for rendering child nodes to DOM nodes that exist outside the parent component.

For example, we need A/B2 input box components whose pull-down results will be notified under the parent component.

As shown in figure:


/ / the parent component
<inputCom />
<inputCom />
<div id="listBox"></div>

// inputCom
class InputCom extends React.Component{
    ExternalCityComp(){
        return return ReactDOM.createPortal(<div>The results of</div>.document.querySelector('#listBox'))    
    }
    public render() {
        return (<div><input onChage={} />{this.ExternalCityComp()}</div>); }}Copy the code

Static static method

The React component can set up static methods, such as implementing methods similar to the Toast component.

class Toast extends React.Component {...static success(Param) { // do something }
	static fail(Param) {    // do something }. } Toast.success() Toast.fail()Copy the code

The className style

const s = require('./index.less');
render (){
const active = true
	return <>
        <li className={` ${s.inputItem} ${s.borderBottom} `} ></li>
        <li className={` ${active ? s.inputItem : s.other } ${s.borderBottom} `} ></li>
    </>
}
                            
Copy the code

Reference picture

import btnImg from './images/floatBtn.png';
render (){
    return <img src={btnImg} />
}                     
Copy the code

4. Introduction to Dva use

In the useDvaBefore, there was always a connectionVuexThe usage is not much different, but the first time to use, chew for a long timeDvaDocuments, or around the dizzy; In fact, it is not complicated, today write how do not understandredux,redux-sagaIn case of pleasant useDva“, the god skipped by himself.

File directory

We only need to focus on the files in three directories.

  • Page:src/pages
  • Model:src/models
  • Services:src/services

The page is the page component that we add to the route, the model is the focus, and the service is basically the encapsulated request request.

Asynchronous request, synchronous request

  1. We treat models as a global variable that holds data and can be retrieved by any page, with apis for storing and retrieving data.
  2. There are two ways to store data, one is to directly store the data in models (synchronous request), and the other is to send a request and store the data in Models (asynchronous request).
  3. Synchronous requestreducersThe methods in themodels.stateData in.
  4. Asynchronous requests are calledmodelstheeffectsMethod, which is calledservicesMethod to get the request data.
  5. The asynchronous request is gettingservicesReturn the data if you want to save tomodels.state, then call the synchronization methodreducersCan.

To refresh your memory: synchronous means save directly, asynchronous means request and save.

Page to obtainmodelsdata

The page gets the data through the dvA.connect method + models.namespace (each model has its own namespace). The main function of the CONNECT method is to merge the data in the Models into the props of the page component. The code lists three different call syntax, if you feel that ES6 decorator + destruct + arrow function is not intuitive, you can look at the code of ES5 version.

Code:

import React from 'react';
import { connect } from 'dva';
// Version 1 decorator syntax
@connect(({ list:{ payInfo, detail } }) = > ({ payInfo, detail }))
class PayInfo extends React.Component<ITextPaperProps.IEntranceState.any> {
    public render() {
        // Get this from props
        const { payInfo, detail } = this.props;
        return (
            <div >
                {payInfo}
            </div>); }}export default PayInfo;

// Version 2 functions
export default connect(
({ list:{ payInfo, detail } }) = > ({ payInfo, detail }))(PayInfo);

Version 3 ES5 functions
export default connect(function(modules){
	return {
    		payInfo: modules.list.payInfo,
        	detail: modules.list.detail
        } 
})(PayInfo);
Copy the code

Page callsmodelsrequest

In the page, call requests through the Dispatch method. Synchronous and asynchronous call forms are the same, but the processing in module is different. The complete code is shown below. Page code:

import React from 'react';
import { connect } from 'dva';
// Version 1 decorator syntax
@connect(({ list:{ payInfo, detail } }) = > ({ payInfo, detail }))
class PayInfo extends React.Component<ITextPaperProps.IEntranceState.any> {

   // Asynchronous invocation
   setPayInfo(){
   	const { dispatch  } = props;
    dispatch({
        type: 'list/setPayInfo'.payload: 'aaaaa'}); }// Synchronous call
   setDetail(){
   	const { dispatch  } = props;
    dispatch({
        type: 'list/setDetail'.payload: 'bbb'}); } publicrender() {
        // Get this from props
        const { payInfo, detail } = this.props;
        return (
            <div >
                {payInfo}
            </div>); }}export default PayInfo;
Copy the code

Models of code:

import { setPayInfoRequest } from '@/services/list';
export default {
    namespace: 'list'.state: {
        payInfo: {},detail:' ',},effects: {*setPayInfo({ payload }, { call, put }) {
            const response = yield call(setPayInfoRequest, payload);
            yield put({
                type: 'setPayInfoReducers'.payload: {
                    res: response,
                },
            });
            returnresponse; }},reducers: {
        setDetail(state, { payload }) {
            return {
                ...state,
                detail:payload
            };
        },
        setPayInfoReducers(state, { payload }) {
            return {
                ...state,
                payInfo: payload, }; ,}}};Copy the code

The services code:

export async function setPayInfoRequest(params) {
  return request('/api/setPayInfo', {
    method: 'POST'.body: {
      ...params
    }
  });
}

Copy the code

Can compare the picture to sort out the idea again, ha ha ha 😄.

5. Wechat configuration

Wechat signature involves more details, more complicated, we step by step.

Wechat domain name authentication

Whether it is wechat, enterprise wechat development, need to first expose the domain name to the external network, to ensure that the external network can access the domain name, and then download the wechat. TXT authentication file in the background, put it in the root directory of the domain name, ensure that the GET request TXT file can be captured by the wechat server.

Example: www.nihaojobo.com/WW_verify_cU0ZETJpcItcKYc8.txt.

For security reasons, o&M personnel may require a list of wechat server IP addresses, as shown in the document.

Wechat obtains Token

This part of the content of the back end, in theory, the front end does not need to care about, or explain, god please skip. In theory, all calls to the wechat server Api require an Access_token, and you need to obtain the access_token according to appID +secret. The access_token will be invalid every 2 hours. Repeated access will cause the last access_token to be invalid.

WeChat login

The wechat login process is as follows. Recently, I have been using Node to develop small programs, which are similar with minor differences. Please refer to the document for detailed information.

  1. Front end access wechat authorized address and beltappidType of authorizationscope, callback addressredirect_uri.
  2. After user authorization or silent authorization, jump to the callback address withcode.
  3. The front endcodeSent to the back end, the back end passesaccess_token + code + appid + secretGet user information, includingopenidAnd user levelaccess_tokenInformation such as,
  4. The back end passes againopenidAnd user levelaccess_tokenObtain and save the user’s nickname and other information.
  5. The backend is uniquely identifiedopenidAnd user data generation their own system leveltokenAnd back to the front end.
  6. Front-end on each requesttoken, according to the backendtokenAssociate users.

Front-end fetch code:

public weChatAuthorize = () = > {
        const param = {
            appid: '* * * * * * * * *'.redirect_uri: encodeURI(window.location.href), // Callback address
            response_type: 'code'.scope: 'snsapi_base'.// snsapi_base Silent snsapi_userinfo authorization
        };
        // Add the wechat authorized address
        const weChatUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize';
        const link = `${weChatUrl}?${qs.stringify(param)}#wechat_redirect`;
        window.location.href = link;
    };
Copy the code

WeChat signature

If the front end needs to invoke SDK interfaces such as uploading pictures and analyzing moments of friends, it needs to execute signature first and then invoke them after passing. The front end gives the Url address of the current page to the back end, and the back end returns the appId, TIMESTAMP, nonceStr, and signature parameters to the front end for permission verification of the wx.config method.


import { Toast } from 'antd-mobile';
// It is the enterprise wechat
export function isWeChatWork() {
    const userAgent = String(navigator.userAgent.toLowerCase());
    return userAgent.includes('wxwork');
};


import { getSignature } from '@/services/home';
import { GETOPENSIGNATURE, } from '@/services/list';

Close the menu in the upper right corner
export function getSignatureWX(cb? :any) {
    cb = cb || function(){}
   // Judge wechat public platform or enterprise wechat signature
   const requestFn = isWeChatWork() ? getSignature : GETOPENSIGNATURE;
   const url = isWeChatWork() ? location.href.split(The '#') [0] : encodeURIComponent(location.href.split(The '#') [0]);
   Toast.hide();
   Toast.loading('Loading'.10000)
   return requestFn({ url }).then(res= > {
       if(res.status === 0){
            Toast.hide();
            const { AppId, Timestamp, NonceStr, Signature,  } = res.data;
            wx.config({
                beta: true.// This must be done otherwise the JsAPI in the form of wx.invoke calls will have problems
                // debug: true, // Enable the debug mode, the return value of all API calls will be displayed in the client alert, to view the passed parameters, you can open the PC, parameter information will be printed in the log, only on the PC.
                appId: AppId, // Required, enterprise wechat corpID
                timestamp: Timestamp, // Mandatory to generate the timestamp of the signature
                nonceStr: NonceStr, // Mandatory to generate a random string of signatures
                signature: Signature, // Mandatory, signature, see appendix - js-SDK Using permission signature algorithm
                jsApiList: ['chooseImage'.'previewImage'.'uploadImage'.'hideOptionMenu'.'onMenuShareAppMessage'.'onMenuShareTimeline'].// Mandatory, a list of JS interfaces that need to be used. Any interface to be called needs to be passed in
            });

            wx.ready( () = > { // It should be called before the user can click the share button
                if(wx.onMenuShareTimeline){
                    wx.onMenuShareTimeline({
                        title: 'Default title'.// Share the title
                        link: window.location.origin + '/groupSendCar'.// Share link, the link domain name or path must be the same as the current page corresponding public number JS security domain name
                        imgUrl: `The ${window.location.origin}/favicon.png`.// Share ICONS}}})); cb(true)
            return true
       }else{
        cb(false)
        Toast.fail('Failed to load wechat signature, please refresh and try again'.3)
        return false}})};Copy the code

Our early stage is the simultaneous development of wechat and enterprise wechat, you can see that there is judgment in the code, in addition to a few small holes, roughly speaking:

  • IOS 12.x wechat public platform must be usedencodeURIComponent, can not contain Chinese.
  • Enterprise wechat and wechatSDKThe version is inconsistent. The enterprise wechat is 1.2.0, and the wechat version is higher.
  • Only one trusted domain name can be set for the test public account.

Image upload

Ensure that the signature is approved before executing the upload event.

  1. chooseImageEvent select image, getlocalIds.
  2. uploadImageEvents to uploadlocalIdsTo obtainserverIdnamelyMediaId.
  3. The front end sends to the back endMediaId, the back end according to the entry to the wechat material address and climb to its own server, back to the front end of the picture address.
  4. The front end saves the image address and displays it.
public upPic = key= > {
        const { dispatch } = this.props;
        // Select the image
        wx.chooseImage({
            count: 1./ / the default 9
            sizeType: ['original'.'compressed'].// You can specify whether the image is original or compressed. By default, both are available
            sourceType: ['album'.'camera'].// You can specify whether the source is photo album or camera, and default is both
            // defaultCameraMode: 'normal', // Indicates the default mode for entering the photo taking interface. Two options are available: Normal and Batch. Normal indicates the single shot mode, and Batch indicates the continuous shot mode. (Note: Users can freely switch between two modes when entering the photo taking interface)
            // isSaveToAlbum: 1, // Integer value, 0 means not saved to the system album when taking a photo, 1 means automatically saved, the default value is 1
            success: res= > {
                const [localId] = res.localIds; // Returns a list of local ids for the selected photo,
                // Upload the enterprise wechat
                wx.uploadImage({
                    localId, // The local ID of the image to upload, obtained by the chooseImage interface
                    isShowProgressTips: 1.// The default value is 1, indicating progress
                    success: (res: { serverId: string }) = > {
                        const serverId = res.serverId; // Return the server ID of the image
                        // Judge wechat public platform or enterprise wechat signature
                        const path = this.isWeChatWork() ? 'home/getPicUrl' : 'list/getPicUrl';
                        Toast.loading('up in'.10000)
                        // Upload the image
                        dispatch({
                            type: path,
                            payload: {
                                MediaId: serverId,
                            },
                        }).then(res= > {
                            Toast.hide();
                            if (this.isWeChatWork()){
                                this.setState({ [key]: res });
                            } else {
                                this.setState({ [key]: res.AbsoluteAddress });
                            }
                            this.merage(); }); }}); },fail:res= >{
                console.log("fail",res)
            },
        });
    };
Copy the code

6. TypeScript components

If the component catalog is not planned in the early stage, and there is no lower-cost way to use components, it is almost impossible to achieve the ideal reuse of components. This is an entropy increasing process, all the preparations must be made in the early stage to ensure that the reuse of components is cheaper than the copy style.

SRC/Components /carUI places business components, including theme styles and utils public methods.

─ ─ carUI ├ ─ ─ Banner ├ ─ ─ Button ├ ─ ─ Coupon ├ ─ ─ the Empty ├ ─ ─ Fixbar ├ ─ ─ Form ├ ─ ─ Input ├ ─ ─ the List ├ ─ ─ Radio ├ ─ ─ the Select ├ ─ ─ Address - the list ├ ─ ─ bank - card ├ ─ ─ check box ├ ─ ─ fixbar - box ├ ─ ─ float - ball ├ ─ ─ index. The TSX ├ ─ ─ init ├ ─ ─ oder - car ├ ─ ─ themes └ ─ ─ utilsCopy the code

Theme style variable

According to the design draft, the theme color is configured into variables in advance to facilitate reuse.

/ / theme color
@color:#F67A23;
/ / the background color
@bg:#F9F9F9;
// Link color
@link:#fa6400;
// Button gradient color
@gradient:linear-gradient(90deg.#ff8c00 0%.#ff4800 100%);
/ / the shadow
@shadow:0px 0px 5px 0px rgba(0.0.0.0.13);
Copy the code

Creating a component Template

In order to create components at a lower cost, init template is placed in the component directory to save code segments that are likely to be used. Components can be directly copied to create, and plop.js can be introduced later without manual input.

Use classNames for handy styling, omit unnecessary arguments, and introduce as much type notation code as possible in propsTypes.

// index.less
@import '.. /themes/index.less';
/ / variable names
// @color
// @bg
// @link
// @gradient
// @shadow

.textCar{
    color: @color;
    box-shadow: @shadow;
}
Copy the code
// index.tsx
import React from 'react';
import classNames from 'classnames/bind';
import omit from 'omit.js';

const styles = require('./index.less');
constcx = classNames.bind(styles); interface ITextPaperProps { className? :string, style? : React.CSSProperties; onMouseEnter? : React.MouseEventHandler<HTMLDivElement>; onChange? :(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) = > void, children? : React.ReactNode; } interface IEntranceState {}class Coupon extends React.Component<ITextPaperProps.IEntranceState.any> {
    constructor(props) {
        super(props);
        this.state = {};
    }

    public render() {
        const {} = this.state
        const { className, onChange } = this.props;
        const nextProps  = omit(this.props,['onChange'.'className']);
        return (
            <div className={cx('textCar', className)} onChange={(e)= > onChange && onChange(e) }>
                <h2 {. nextProps} >default component</h2>
            </div>); }}export default Coupon;
Copy the code

H5 Tips

Pop-up layer page

In the H5 scenario, there are many requirements for pop-up layer interaction, such as selecting address book and coupons, etc. Ant Mobile provides the component of pop-up box, but it is not flexible in actual use. The simplest thing is to realize it by yourself.

/ / CSS
.CouponsSelectBox{
    position: absolute;
    height: 100vh;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch; // Resolve div scroll bar lag
}

/ / JSX parts
{showCoupon && <CouponsSelect value={selectCoupon} onChange={res= > { this.setState({ selectCoupon:res, showCoupon:false })}}/>}
Copy the code

Input onCompositionEnd event

Due to the input method, onChange event can be triggered when Struggling sound chooses Chinese characters, and onCompositionEnd event can be used to handle it.

Loading cover

In the future, most pages will avoid multiple clicks or poor network conditions. Loading effect is suggested to improve user experience, or it can be encapsulated in Request. Ant Mobile’s Toast component is very convenient.

Toast.loading('In order'.10000)
const { dispatch } = this.props;
return dispatch({
    type: 'home/placeOrder'.payload: param,
}).then(res= > {
    Toast.hide();
    router.push('/order/detail? order_no=' + res.wl_order_no);
});
Copy the code

8. Log

C-facing users generally need to record exposure, click and other key node information; In addition, in order to distinguish whether the user has cache or not, the front-end code version needs to be added to the front-end code.

Exposure of the log

The exposure log event is triggered in SRC /pages/Authorized.js and each route switch is performed.

const Authority = getAuthority();
const Authorized = RenderAuthorized(Authority);

// Path and log configuration
const LogIdList = {
    '/groupSendCar': 1-1 ' '.'/oderPerfect': '2-2'.'/payInfo': '3-3'.'/order/list': 4-4 ' '.'/order/detail': '5-5'.'/myBill': '6-6'.'/couponsList': 7 '7'};const AuthorizedCom = (props) = > {
    const { children, dispatch } = props;
    useEffect(() = > {
        const { pathname } = children.props.location;
        if (LogIdList[pathname]) {
            const [funcModule, eventName] = LogIdList[pathname].split(The '-');
            dispatch({
                type: 'global/log'.payload: {
                    product: 1.platform: 1.funcModule: Number(funcModule),
                    eventName: Number(eventName)
                }
            });
        }

    }, [children.props.location]);

    return (
        <Authorized authority={children.props.route.authority} noMatch={<Redirect to="/exception/403" />}>
            {children}
        </Authorized>
    );
};

export default connect(({ global, home, list }) = > ({ home, list, userType: global.userType }))(AuthorizedCom);
Copy the code

Click on the event log

Click event in SRC/layouts/BasicLayout trigger in js, can’t be handwritten function call each click of the place, to the body in the componentDidMount binding global click event, if you click on the element contains data – log properties commit log, Where logs are needed, the element is added to the data-log attribute, and the logID entry parameter is kept in the component directory.

 componentDidMount() {
    const _this = this;
    document.body.addEventListener('click'.function(e){
      const logString = e.target.getAttribute('data-log');
      if(logString){
        const [ funcModule, eventName ] = logString.split(The '-');
        _this.sendLog(Number(funcModule),Number(eventName)); }}); }// Click log
  sendLog = (funcModule,eventName='sd') = > {
    const { dispatch, } = this.props;
    dispatch({
        type: 'global/log'.payload: {
            product:1.platform:1,
            funcModule,
            eventName
        }
    });
 }
Copy the code

Front-end code version

The reason is that there have been several bugs in the front end. I don’t know whether it is a cache or a logic problem. Originally, I wanted to add version variable to the environment variable, modify the variable every time we publish, and then add version attribute to each request header. Setting the versionTime property in the environment variable to the package time generated by moment makes it easier to determine which front-end version the request is coming from. Config /config.js to the define property.

{
  versionTime:moment('YYYY-MM-DD hh:mm:ss')}Copy the code

9. Other

MD5 encryption

Theoretically, front-end MD5 is almost useless, because the encryption value is also in the front, we also do encryption, in the words of the leader, to increase the complexity.

Encryption process:

  1. The parameter objects are sorted by key and serialized as strings.
  2. useCryptoJS.MD5Concatenation follows the back-end convention: serialized string + secret key + date string.
  3. Send as required by the backendsn+ backend conventionfrom+ Original parameters.
  4. The backend verifies the data in a specified format and returns service data.

// Parameter sort serialization
const paramStringify = params= > {
    const newParams = {};
    / / sorting
    Object.keys(params)
        .sort()
        .forEach(key= > {
            newParams[key] = params[key];
        });
    return qs.stringify(newParams, { encode: false });
}

WeChatWorkSecret and WeChatWorkAppkey are environment variables
const OAAddSalt = params= > {
    const str =  params ? paramStringify(params) :' ';
    // Add salt to generate MD5
    const sn = CryptoJS.MD5(str + WeChatWorkSecret + moment().format('YYYY-MM-DD')).toString();
    return { ...params, sn, from: WeChatWorkAppkey };
};

// Obtain wechat authorized signature
export async function getPicUrl(params) {
    return request(WeChatWorkApi + '/WeChat/WechatPicUrl', {
        method: 'POST'.headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: OAAddSalt(params),
    });
}
Copy the code

Real machine commissioning

Such as verification of wechat signature, picture upload and other functions, or need to be debugged under the real machine. Sudo apachectl -k stop to stop Apache from using port 80 by default.

Process:

  1. Local startup front-end service guarantee127.0.0.1Can be accessed.
  2. Set host to the authentication domain name locally, for example:127.0.0.1 nihaojob.com.
  3. Start thecharlesAgent software.
  4. Mobile phone set proxy to computer IP andcharlesProxy port.
  5. Mobile phone wechat access authentication address.

In addition, Charles needs to install a certificate to capture HTTPS packets. Both mobile phones and computers need to install certificates, otherwise the captured packets will be garbled characters.

conclusion

Generally speaking, the knowledge is very shallow, unlike notes like React source code parsing, but for a Vue to React start wechat H5 development practice for me, or harvest quite a lot.

  1. Use the React base Api.
  2. Familiar with wechat signature, login and other processes.
  3. TypeScript components gain experience.
  4. React development ecology Umi+Dva familiar.

The online preview

The recommended project template on the Dva website is UMI-DVA-ANTD-Mobile.