preface

Recently, I am learning Rax official documents. I have been following the scripts closely since the front end, and I have no basis for React. This paper records what I need to learn from reading Rax official documents and what I encountered in actual combat.

Before this, follow to the cross-end players: support taobao 100 million days of cross-end framework — Rax introduction tutorial (with TODO Demo) article (this article name is really long) over a primer case.

Basic guidelines

The directory structure

├── public │ ├─ favicon.png ├── SRC │ ├─ app.json │ ├─.pdf │ ├─.pdf ├ ─ ─ app. Ts # [small program | SPA] application gateways │ ├ ─ ─ miniapp - native / # [little] program native code │ ├ ─ ─ the components / # custom business components │ ├ ─ ─ pages / # page │ ├ ─ ─ ├── package.json ├─ tsconfig.jsonCopy the code
  • When committing git, you should ignore the.rax/ temporary directory

  • Pages: indicates the page file directory of the project. The recommended directory format is as follows.

    └── model.ts # ├── index.tsx # ├─ index.module. SCSS #Copy the code
    • Note that the page style file isindex.module.scssRather thanindex.scss
    • Created on the current pagecomponentsThe directory holds the current page-level components.
  • / SRC /components: This directory holds the common components at the current project level that can be used by other pages or other components.

  • Models / : The global data model directory for a project, usually containing multiple Model files.

    • Exercises / ├── foo.ts ├─ bar.tsCopy the code
  • Json: is responsible for the runtime configuration of the application, such as routes and small program window configuration.

  • Json: is the configuration file related to the entire application build. For details, see Project Configuration.

  • Tsconfig. json: Configuration file required for TypeScript compilation.

debugging

Package. json configures the following command

{ "scripts": { "start": "rax-app start", ... }}Copy the code
  • Run NPM run start to start project development

  • In normal cases, the browser is automatically opened to preview the page by default after you run the command

  • Hot update: The browser will automatically refresh the page after modifying the source code.

  • The complete parameters of the start command are as follows:

    • $ rax-app start --help Usage: rax-app start [options] Options: -p, --port <port> Service port number -h, --host <host> Name of the service host --config <config> Specifies the configuration file -- HTTPS enabled -- Analyzer-target Specifies the task to perform build analysis, For example, '-- Analyzer -target=web' -- Analyzer -port <port> Supports custom build analysis ports --disable-reload disables hot update modules --disable-mock disables mock services --disable-open Disables the default browser opening. --enable-assets Enables the command line to output Webpack Assets. -- Mpa-entry Specifies the page to be compiled for a multi-page applicationCopy the code
    • Example: modify the “start” command in package.json: “rax-app start –port 3000”

    • When a port occupancy conflict occurs, RAx prompts you to run the next port number.

build

Package. json configures the following command

{ "scripts": { "build": "rax-app build", ... }}Copy the code
  • The NPM run build is executed to build the project, and the build product is output to the./build directory by default.

  • The complete parameters supported by the build command are as follows:

    • $ rax-app build --help Usage: rax-app build [options] Options: --analyzer-target Specifies the task to perform the build analysis, for example, '-- Analyzer-target =web' -- Analyzer-port <port> Supports custom build analysis ports --config <config> Specifies the configuration fileCopy the code

The problem

  • For the first time to performnpm run buildError encountered:PostCSS plugin postcss-discard-comments requires PostCSS 8.: Error: PostSS discard comments requires PostSS 8.

This error is that the postCSs-discart-comments version of the engineering plug-in does not match the postCSS version, so there are two solutions:

  1. To reducepostcss-discard-commentsversion
    • You need to know what low version of the plug-in corresponds to, and it’s not clear how low a version can be packaged and built
  2. Install the latest versionpostcss
    • npm i postcss -S
  • The result is executed againnpm run buildIt’s ready to pack, butnodeThe plug-in reported an error building PostCSS.

For more information, please visit github.com/mrnocreativ… Here’s the answer.

PostCSS8 is not compatible with Node NPM. NPM is still using PostCSS 6.0.1, and NPM is only using PostCSS 6.0.1

Finally, I went to the Rax nail community and got a quick answer: CNPM is locked and can be used to install plug-ins. That is to say, the CNPM installed plug-in has degraded its CSS plug-in, thus addressing the compatibility issue.

To solve

  1. Delete the node_modules folder.
  2. usecnpm installPerform dependency installation.
  3. Perform this operation after the installation is successfulnpm run build

episode

From origin ‘null’ has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https. Cross-domain problems.

The reason is that the Google File protocol has cross-domain problems when accessing local files

The solutions are as follows.

  1. Build a local server and run on the server to access local files.

  2. Use idea plug-in to open, such as webStrom open HTML file, will automatically open the service.

  3. The Anywhere plug-in starts the service:

  4. Install the NPM I Anywhere plug-in globally

  5. Go to the corresponding folder directory and run the service anywhere

  6. The cross-domain problem of browsers accessing local files can be solved by making the current directory the root directory of a static file server.

Project configuration

By default, the rax-App project configuration is aggregated in the build.json file in the root directory of the project. The default configuration format is JSON.

Details in the official document

Environment configuration

Rax App allows you to differentiate between different environments, allowing you to differentiate between runtime configurations based on your environment.

Set up the environment

Both start and build environments are supported by default. Instead of rax-app start/build, developers can extend the environment by using the –mode argument:

{
  "scripts": {
    "start": "rax-app start --mode local"."build:daily": "rax-app build --mode daily"."build": "rax-app build --mode prod"}}Copy the code

Distinguish engineering configuration

Once the environment is defined, we need to distinguish configurations according to the environment.

Use the modeConfig property in build.json

{
  "modeConfig": {
    "prod": {
      "define": {},
      "minify": false
    },
    "daily": {
      "define": {},
      "minify": true}}}Copy the code

At the same time, the local plugin build.plugin.js can also get the current mode from the context:

module.exports = ({ context }) = > {
  const { command, commandArgs } = context;
  const mode = commandArgs.mode || command;
}
Copy the code

Distinguish between runtime configurations

After the environment is defined, the front-end code can get the current environment through APP_MODE:

import { APP_MODE } from 'rax-app';

console.log('APP_MODE', APP_MODE);
Copy the code

Of course, most of the time you don’t need to worry about the APP_MODE variable, as long as you use the convention to distinguish between different environment configurations. Write the configuration for the different environments in SRC /config.ts:

Export default {// default configuration default: {appId: '123', baseURL: '/ API '}, local: {appId: '456',}, daily: { appId: '789', }, prod: { appId: '101', } }Copy the code

After configuration, the framework automatically merges the configuration and overwrites it according to the current environment. Developers simply need to use config directly in their code:

import { config } from 'rax-app';
console.log(config.appId);
Copy the code

Unexpected console statement no-console needs to be set esLint “no-console”:”off” or window.console.log(), Did not know how to configure the.eslintrc.js file in the Rax framework.

There is a better way to learn records

Dynamically extend the runtime environment

When an application has two runtime environments, daily/pre-release, and can actually only perform one build task, you can extend the runtime environment to support different configurations.

In SRC /config.ts, extend the environment by domain name:

// SRC /config.ts: Dynamically extend the environment: two ways

// Mode 1. Output global variables on the page through the server
window.__app_mode__ = window.g_config.faasEnv;  // window.g_config.faasEnv can also be window.__env__
  
// Method 2. Dynamic determination based on the URL
if (/pre.example.com/.test(window.location.host)) {
  window.__app_mode__ = 'pre';
} else if (/daily.example.com/.test(window.location.host)) {
  window.__app_mode__ = 'daily';
} else if (/example.com/.test(window.location.host)) {
  window.__app_mode__ = 'prod';
} else {
  window.__app_mode__ = 'local';
}

export default {
  default: {},
  daily: {},
  pre: {},
  prod: {}};Copy the code

Set the global variable __app_mode__.

Write a component

Component

Components, similar in concept to Javascript functions. It takes the data (Props) passed in from the parent element and returns the Rax element that describes what the page displays. Components break our UI into separate, reusable parts, and each part can be maintained separately.

There are two ways to define a Component: Function Component and Class Component.

Function Component

Is the easiest way to define components, which makes the code cleaner and allows you to use Hooks to write components that are easier to maintain and extend. We recommend using Function Component to define a Component:

import { createElement } from 'rax';

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
Copy the code

In addition, we can write a Class Component using the ES6 class:

import { createElement, Component } from 'rax'; class Welcome extends Component { render() { return <h1>Hello, {this.props.name}</h1>; }}Copy the code

Note that the Rax component must be capitalized when named. Components that start with a lowercase letter are treated as native DOM tags.

In field

Two component definition methods

  • For Cross-end players: Introduction to Rax, a cross-end framework that supports hundreds of millions of days on Taobao

  • Class Component: Encapsulates its own Class case ListItem2

    • ListItem2/index.tsx
    import { createElement, Component } from 'rax'; import View from 'rax-view'; import Text from 'rax-text'; class ListItem2 extends Component<{ // eslint-disable-line id? : Number; done? : Boolean; content? : String; onClick: (id? : Number) => void; }> { style = { fontSize: '64rpx', lineHeight: '96rpx', textDecoration: this.props.done && 'line-through', }; render() { return ( <View className="list-item" onClick={() => this.props.onClick(this.props.id)}> <View className="list-dot" /> <Text style={this.style}>{this.props.content}</Text> </View> ); } } export default ListItem2;Copy the code
    • List/index.tsxcall
    . import ListItem2 from '.. /ListItem2'; . <View className="list-item-wrapper"> {list.map((item) => ( <ListItem2 key={item.id} id={item.id} content={item.content} done={item.done} onClick={handleItemClick} /> ))} </View>Copy the code

Realized effect:

BUG

  • The node version does not match

The node version is >= 13.9.0

  • esLinstStrict rule warning

Since esLint doesn’t recommend using class components, we’re just an exercise, and there are several methods that let esLint ignore the class declaration on line 5 of the file.

Props

Props is an incoming parameter that a component accepts to receive data from its parent node. Props are read-only, and you cannot change the Props value in the current component.

State

State is a private variable controlled by the component itself and, together with Props, controls the rendering of the component. We use the useState Hook to define and manage State. In the following example, we use useState to implement a counter:

import { createElement, useState } from 'rax';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Copy the code

The useState argument is the initial state, which returns an array. The first value of the array is state, and the second value is a function that changes state. In the example above, count and setCount are paired one by one, and the value of count can only be changed by setCount.

Fragment

Fragment is a special component that combines multiple elements together without creating additional DOM elements. In raX component rendering, if you need to render more than one element, you need to use fragments to wrap the element inside.

import { createElement, Fragment } from 'rax';

function Hello() {
  return (
    <Fragment>
      <h1>Hello</h1>
      <h1>World</h1>
    </Fragment>
  );
}
Copy the code

The Fragment component supports only key attributes and some JSX+ attributes (x-if X-for X-slot). Other attributes are not supported. We can also use <> when the Fragment component does not need properties.

import { createElement } from 'rax';

function Hello() {
  return (
    <>
      <h1>Hello</h1>
      <h1>LinM</h1>
    </>
  );
}
Copy the code

<> The label must be on the outside and cannot be wrapped by other labels. Otherwise, the following returns:

Hooks

Hooks are a new feature in Rax 1.0 that lets Function components use state and life cycles. Rax is implemented in accordance with the React Hooks standard.

Commonly used Hooks

useState

UseState is used to define and manage local state. In the example above, we use useState to implement a counter:

  • useStateThe argument is the initial state, which returns an array.
    • The first value of the array is state, and the second value is a function that changes state.
  • In the example above, count and setCount are paired one by one, and the value of count can only be changed by setCount.
useEffect

The useEffect code is run after each rendering, including the first rendering:

import { createElement, useState, useEffect } from 'rax';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() = > {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Copy the code

UseEffect Has the same use as componentDidMount, componentDidUpdate, and componentWillUnmount in Class Components, but is combined into a single API.

It’s very similar to Vue3’s useEffect API, so I’m going to use the analogy.

Hooks use rules

Hooks are JavaScript functions, but using them comes with two additional rules:

  • Hooks call only at the top of a function, never inside loops, conditions, or subfunctions.
  • Only call Hooks in Rax function components and custom Hooks, never in other JavaScript functions.

JSX

const element = <p>Hello, world!</p>;
Copy the code

This HTML tag-like syntax is called JSX. JSX is a syntax extension to JavaScript that we use in Rax to describe page structure. The following is a brief introduction to the basic use of JSX.

JSX is declared in the same way as normal HTML tags, wrapped in <> tags, or nested:

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);
Copy the code

If there are no children in the tag, you can use /> to close the tag, which is a single tag:

const element = <img src="url" />
Copy the code

expression

You can insert any JavaScript expression in JSX. Expressions in JSX must be written inside curly braces {} :

const element = <h1>{'Hello' + ',' + 'world! '}</h1>;
Copy the code

You cannot use an if else statement in an expression, but you can use the ternary operator a? B: C to achieve conditional selection.

const element = <h1>{true ? 'True! ' : 'False! '}</h1>
Copy the code

Use JSX arrays

You can nest an array containing multiple elements within a single JSX element, and each JSX element in the array will be rendered individually:

const arr = [
  <span>Hello, world!</span>.<span>Hello, Rax!</span>,];const element = <p>{arr}</p>;
Copy the code

Comment in JSX

JSX comments, like expressions, must be written in curly braces {} :

const element = <p>{/ * comment... */} Hello, world!</p>;
Copy the code

In field

import { createElement } from 'rax';
import View from 'rax-view';

function Jsx() {
  const element = <h1>{'Hello, world! '}</h1>;
  const arr = [
    <span>Hello, world! </span>.<span>Hello, Rax!</span>,];const comment = <p>{/* comment... */ } Hello, world!</p>;
  return (
    <View>General statement:<div>
        <h1>Hello!</h1>
        <h2>Good to see you here.</h2>
      </div>Expressions:<div>
        {element}
      </div>Using JSX arrays:<div>
        {arr}
      </div>Note in JSX:<div>
        {comment}
      </div>
    </View>
  );
}

export default Jsx;

Copy the code

JSX + syntax

Rax supports a JSX extension syntax, JSX+, which helps business developers write JSX more easily and faster. JSX+ is not a new concept; it is an extended instruction concept based on JSX. The specific syntax is as follows:

1. Conditional judgment

Grammar:

<View x-if={condition}>Hello</View>
<View x-elseif={anotherCondition}></View>
<View x-else>NothingElse</View>
Copy the code

Note: X-elseif can occur more than once, but the order must be X-if -> x-elseif -> X-else, and these nodes are sibling nodes. If the order is wrong, the instruction is ignored.

2. Loop lists

Grammar:

{/* Array or Plain Object*/}
<tag x-for={item in foo}>{item}</tag>
  
<tag x-for={(item, key) in foo} >{key}: {item}</tag>
Copy the code

Note: 1. If the loop object is an array, key indicates the loop index and its type is Number. 2. When x-for and x-if are used on the same node, the loop priority is greater than the condition, i.e. the item and index of the loop can be used in the subcondition judgment.

3. Single render

The createElement is triggered only when it is first rendered and referenced to the cache, and the cache is reused directly when re-render to improve the rendering efficiency and Diff performance of unbound nodes.

Grammar:

<p x-memo>this paragragh {mesasge} content will not change.</p>
Copy the code

4. Slot instruction

Similar to WebComponents’ slot concept and provides slot scope.

Grammar:

<tag x-slot:slotName="slotScope" />
Copy the code

Example:

// Example
<Waterfall>
  <view x-slot:header>header</view>
  <view x-slot:item="props">{props.index}: {props.item}</view>
  <view x-slot:footer>footer</view>
</Waterfall>
<slot name="header" /> / / slot
Copy the code

5. Fragment components

When using the x-if x-for x-slot directive, if we don’t want to generate meaningless elements, we can use the Fragment component.

Use:

<Fragment x-if={condition}>
  <div />
</Fragment>
Copy the code

6. Class name binding

Grammar:

<div x-class= {{item: true.active: val }} />
Copy the code

Reference implementation:

<div className={classnames({ item: true.active: val})} />
Copy the code

Classnames method capabilities refer to the NPM package of the same name.

Custom Hooks

Custom Hooks enable you to extract component logic into reusable functions.

import { createElement, useState, useEffect } from 'rax';

const useDocumentTitle = function(title) {
  useEffect(
    () = > {
      document.title = title;
    },
    [title]
  );
};

function Example() {
  const [count, setCount] = useState(0);
    useDocumentTitle(`You clicked ${count} times`)
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()= > setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Copy the code

In the example above, useDocumentTitle is a custom Hooks that execute the logic that sets the title in other components. We say that a function is a custom hook if its name begins with “use” and it calls other Hooks.

Hooks correspond to the life cycle of a class component

At this point, I believe you have a basic understanding of Hooks. The lifecycle of a class component can be implemented using the following Hooks:

  • useEffect
    • componentWillUpdate
    • componentDidMount
    • componentWillUnmount
  • useLayoutEffect
    • Consistent with useEffect, used to get the render result of an element
  • UseMemo, useCallback
    • shouldComponentUpdate

UseEffect to complete componentWillUpdate as an example:

function DemoComponent() {
  useEffect(() = > {
    // ...
  });
  return <p>Hello hooks!</p>
}
Copy the code

UseEffect to implement componentDidMount, for example:

function DemoComponent() {
  useEffect(() = > {
    // useEffect is called only when componentDidMount and componentWillUnMount are used.} []);return <p>Hello hooks!</p>
}
Copy the code

UseEffect (componentWillUnmount) useEffect (componentWillUnmount) useEffect (componentWillUnmount) useEffect (componentWillUnmount)

function DemoComponent({source}) {
  useEffect(() = > {
    const subscription = source.subscribe();
    return () = > {
      // componentWillUnMount calls the callback returned by the first argument.
      subscription.unsubscribe();
    };
  }, [source]); // Indicates that when the source changes, it is executed once
  return <p>Hello hooks!</p>
}
Copy the code

ShouldComponentUpdate can be implemented using Memo. For example:

function DemoComponent(props) {
  return <h1>I am {props.name}. hi~</h1>;
}
memo(DemoComponent, (prevProps, nextProps) = > prevProps.name === nextProps.name);
// Add a second argument to specify a custom comparison function that takes the old props and the new props. If true is returned, the update is skipped.
Copy the code

More and more

  • Introducing JSX
  • JSX In Depth
  • Introducing Hooks

The style package

Non-inline Styles (CSS)

For applications delivered to the Web or applets, we strongly recommend using standard CSS for styling.

Global style

The global style for the whole project, recommend a unified definition in SRC/global. [CSS | less | SCSS] document, the framework will automatically into the file:

body {
  -webkit-font-smoothing: antialiased;
}
a {
  color: red;
}
Copy the code

Component style

For both page – and component-level styles, we recommend using the CSS Modules scheme, which is a good solution to global pollution and name conflicts in style development.

The specific rules are as follows:

  • File name: The convention file name format isxxx.module.[css|less|scss]
  • Modularity: one style file per page or component
Home ├── index.module.css ├─ index.tsxCopy the code

First write the style file content:

/** ./pages/Home/index.module.css */
.container {
  background: #fff;
  width: 750rpx;
}

/* You can also define global styles via the :global syntax of CSS Modules:global { body { a { color: blue; }}}Copy the code

Introduce the corresponding style file into the file and associate the className with the corresponding style:

// ./pages/Home/index.tsx
import styles from './index.module.css';

function Home() {
  return (
    <View className={styles.container}>
      <View>CSS Modules</View>
    </View>
  );
}
Copy the code

If you look at the DOM structure of this example in your browser, you’ll see what it actually looks like:

<View class="container--1DTudAN">title</View>
Copy the code

Inline style

If inlineStyle: true is configured in build.json, the entire project uses the inlineStyle. For Weex, Kraken and other rendering engines that do not yet support CSS, only inline styles are supported (that is, styles are set by the style attribute of the element).

const myStyle = {
  fontSize: '24px'.color: '#FF0000'
};

const element = <View style={myStyle}>Hello Rax</View>;
Copy the code

Note: Inline styles do not support capabilities such as @keyFrames in CSS

It also supports writing styles in CSS files, and then compiling tools to generate inline styles, as follows:

index.css

/** index.css */
.container {
  background: #f40;
  padding: 30rpx;
}
Copy the code

index.tsx

import { createElement } from 'rax'; import './index.css'; export default function Title() { return (<View className="container">... </View>); }Copy the code

In inline scenarios, you can also use the CSS Modules naming conventions and writing methods to ensure that the styles in both modes are basically the same.

Adaptive unit RPX

Rax uses RPX (Responsive Pixel) as the cross-end style unit, which can be adapted according to the screen width. Let’s say the screen width is 750rpx. In the case of the iPhone6, the screen width is 375px, so 750rpx = 375px = 100vw, so in the iPhone6, 1rpx = 0.5px = 100/750vw.

equipment RPX convert px (screen width / 750) Px conversion RPX (750 / screen width)
iPhone5 1rpx = 0.42px = 100/750vw 1px = 2.34rpx = 234/750vw
iPhone6 1rpx = 0.5px = 100/750vw 1px = 2rpx = 200/750vw
iPhone7 Plus 1rpx = 0.552px = 100/750vw 1px = 1.81rpx = 181/750vw

It is recommended that designers use the 750 as the standard for design sketches when developing Rax pages

Migrate from inline to non-inline styles

Some Web applications may have historically used an inline style solution, and for those applications we recommend migrating to a non-inline style to use more powerful CSS capabilities.

Remove the inlineStyle property or set it to false:

// build.json{-"inlineStyle": true
}
Copy the code

Make sure that the original className is not in conflict. If so, fix it or migrate to CSS Modules.

CSS preprocessor

The Rax App supports Less and Sass preprocessors by default, so you just need to name your files according to.less or.scss rules. If you’re using Sass, you’ll also need to add a Sass compiler such as the community recommended Dart-sass:

$ npm i --save-dev sass
Copy the code

Dark mode adaptation

You can set the corresponding theme color through media.

@media (prefers-color-scheme: dark) { .container { background-color: #111; }}Copy the code

The CSS Modules file type is incorrect

If you cannot find the module “./index.module. CSS “or its corresponding type declaration, you need to create a new typings.d.ts file under SRC / :

// src/typings.d.ts
declare module '*.module.css' {
  const classes: { [key: string] :string };
  export default classes;
}
Copy the code

CSS Modules Official document

Advanced guide

Framework apis

Framework apis can normally be imported via rax-app, e.g. : import {runApp} from ‘rax-app’;

basis

runApp

For SPA applications or applet applications, SRC /app.tsx is the entry point for the entire application and runs the application via runApp() :

import { runApp } from 'rax-app';

runApp({});
Copy the code

Object parameters to be configured

When you include JSX elements in app.ts, you need to rename app.ts to app.tsx.

App.getinitialdata () allows you to obtain some asynchronous states before application startup, such as logon status judgment, user information acquisition, etc.

import { runApp } from 'rax-app'; const appConfig = { + app: { + getInitialData: async () => { + // const data = await fetch('/api/data'); + return { userId: '123' }; +} +},}; runApp(appConfig);Copy the code

The obtained initial data can be consumed anywhere via the getInitialData API:

import { createElement } from 'rax';
+import { getInitialData } from 'rax-app';

export default = () => {
+  const initialData = getInitialData();
  console.log(initialData.userId);
};
Copy the code

APP_MODE

Obtain the application environment.

ErrorBoundary

A component for the error boundary.

IAppConfig

Type definition of appConfig.

+import { runApp, IAppConfig } from 'rax-app';

+const appConfig: IAppConfig = {
  app: {}
+}

runApp(appConfig);
Copy the code

State management

You can disable state management by setting store: false in build.json.

store

Application-level Store instances.

Define the type of

IStoreModels

Define the type of the model.

import { IStoreModels, IStoreDispatch, IStoreRootState } from 'rax-app';

// Define the model type.
interface IAppStoreModels extends IStoreModels {
};

const models = {};
// Define the type of Dispatch.
export type IRootDispatch = IStoreDispatch<typeof models>;
// Define the type of RootState.
export type IRootState = IStoreRootState<typeof models>;
Copy the code

routing

getHistory

GetHistory is used to get the history instance.

import { getHistory } from 'rax-app';

function HomeButton() {
  const history = getHistory();

  function handleClick() {
    history.push('/home');
  }

  return (
    <button type='button' onClick={handleClick}>
      Go home
    </button>
  );
}
Copy the code

getSearchParams

Only SPA and small programs are supported. MPA is not supported

Used to resolve URL parameters. Assuming that the current URL is https://example.com?foo=bar, the parsing query parameters are as follows:

// src/components/Example
import { getSearchParams } from 'rax-app';

function Example() {
  const searchParams = getSearchParams()
  // console.log(searchParams); => { foo: 'bar' }
}
Copy the code

withRouter

You can get the history, location, and match objects of a route by adding a withRouter decorator to the Class component.

import { createElement } from 'rax';
import { withRouter } from 'rax-app';

function ShowTheLocation(props) {
  const { history, location } = props;
  const handleHistoryPush = () = > {
    history.push('/new-path');
  };

  return (
    <div>
      <div>Current path: {location.pathname}</div>
      <button onClick={handleHistoryPush}>Click to jump to a new page</button>
    </div>
  );
}

export default withRouter(ShowTheLocation);
Copy the code

history

The route instance of the application is obtained.

import { history } from 'rax-app';

// Get the number of entities in the history stack
console.log(history.length);

// Get the action for the history jump. There are three types: PUSH, REPLACE, and POP
console.log(history.action);

// To get the location object, including pathname, search, and hash
console.log(history.location);

// Used for route jump
history.push('/home');

// For route replacement
history.replace('/home');

// Used to jump to the previous route
history.goBack();
Copy the code

Security zone adaptation

Safe Area refers to the visible window in the screen that is not affected by corners (corners), fringe (sensor housing), small black bar (Home Indicator) and other elements.

WebKit introduced a series of apis from iOS11 to get the location of security zones. For example, the environment variable safe-area-inset-top can be used to obtain the distance between the security area and the top of the viewport, that is, the height of the fringe area. You can run safe-area-inset-bottom to obtain the distance between the security zone and the bottom of the viewport, that is, the height of the small black bar.

Bang screen fit

To get the bang height, you first need to set the viewport-fit and adjust the layout of the visual window. If and only if viewport-fit is set to cover, you can further set the security zone range of the page.

<meta name="viewport" content="width=device-width, viewport-fit=cover">
Copy the code

Then, combined with the env() method, you can get the safe-area-inset-top value and use it as the padding-top value for the container node. Before iOS 11.2, use the constant() method.

.root {
  padding-top: constant(safe-area-inset-top); Compatible with iOS < 11.2 */
  padding-top: env(safe-area-inset-top); // /* iOS > 11.2 */
}
Copy the code

The little black bar fits

Similar to bang-screen adaptation, the small black bar adaptation can be achieved by adjusting the padding-bottom value of the tabbar to add more white space. Adjust the security area by setting the padding value of the tabbar:

.tabbar {
  padding-bottom: 0; /* If there is no small black bar, no additional */ is required
  padding-bottom: constant(safe-area-inset-bottom); Compatible with iOS < 11.2 */
  padding-bottom: env(safe-area-inset-bottom); // /* iOS > 11.2 */
}
Copy the code

The animation program

For basic animations, the recommended approach is to combine the CSS properties Transition or Animation.

Transitions

CSS Transitions provides a way to control the speed of animations when changing CSS properties. It makes property changes a process that lasts over a period of time, rather than taking effect immediately. For example, changing the color of an element from white to black is usually effective immediately. Using CSS Transitions, the color of the element will gradually change from white to black at a curving rate. This process can be customized.

Example:

When the hover reaches the node, the width and height of the node, the background color, and the transform value are gradually changed.

/* index.module.css */
.box {
  border-style: solid;
  border-width: 1px;
  width: 100rpx;
  height: 100rpx;
  background-color: #0000FF;
  -webkit-transition:width 2s, height 2s,
      background-color 2s, -webkit-transform 2s;
  transition:width 2s, height 2s, background-color 2s, transform 2s;
}

.box:hover {
  background-color: #FFCCCC;
  width:200rpx;
  height:200rpx;
  -webkit-transform:rotate(180deg);
  transform:rotate(180deg);
}
// index.jsx
import styles from './index.module.css';

export default() = > {return <div className={styles.box} />;
};
Copy the code

Animations

CSS animations provide the ability to create simple animations with keyframes. By specifying the keyframes for the start, end, and intermediate points of the animation using @keyframes, each frame describes how the animation elements should be rendered at a given point in time (CSS style configuration), making it very easy to create animation sequences.

Example: Using animation to achieve node display, from the right side of the screen into the effect.

/* index.module.css */
.box {
  width: 100rpx;
  height: 100rpx;
  background-color: #0000FF;
  animation-duration: 3s;
  animation-name: slidein;
}

@keyframes slidein {
  from {
    margin-left: 100%;
  }

  to {
    margin-left: 0%;
  }
}
// index.jsx
import { createElement, useState } from 'rax';
import View from 'rax-view';

import styles from './index.module.css';

export default function Home() {
  const [showBox, setBox] = useState(false);

  function onClick() {
    setBox(true);
  }

  return (
    <View>
      <View className={styles.buttom} onClick={onClick}>show box</View>
      {
        showBox && <View className={styles.box}></View>
      }
    </View>
  );
}
Copy the code

Runtime static configuration

The SRC /app.json command is used to configure an application globally, including routing, window representation, and rendering mode. Example of default configuration:

{" routes ": [{" path" : "/", "source" : "pages/Home/index"}], "the window" : {" title ":" Rax App 1.0 "}}Copy the code

Configure routes on a single page

The routing configuration

In SRC /app. TSX, we can configure routing information such as route type and base path as follows:

import { runApp } from 'rax-app'; const appConfig = { router: { type: 'browser', basename: '/seller', fallback: <div>loading... </div> } }; runApp(appConfig);Copy the code

Page routing

For single-page applications, routes in SRC /app.json are used to specify the page of the application:

  • path: Specifies the routing address of the page
  • source: Specifies the address of the page componentpages/[PAGE_NAME]/indexFormat, nested routing is not supported
  • targetsSpecifies the side of the page to be built. The default isbuild.jsonThe configuredtargetsThe value of the
  • window: Specifies the form representation for this page, which can override global window Settings
{
  "routes": [                    
    {
      "path": "/",
      "source": "pages/Home/index"
    },
    {
      "path": "/about",
      "source": "pages/About/index",
      "targets": ["web"]
    }
  ]
}
Copy the code

By default, the framework has routing enabled, and each page is packaged into a separate bundle

window

You can set the window presentation of your application, and you can also set the window presentation for each page. Currently, the following parameters are supported:

attribute describe
title The page title
{ "routes": [ { "path": "/", "source": "pages/Home/index" }, { "path": "/about", "source": "Pages/About/index", "window" : {" title ":" About Rax "}}], "the window" : {" title ":" default title "}}Copy the code

tabBar

If your app is a multi-tab app (the bottom bar allows you to switch pages), you can use the tabBar configuration to specify what the TAB bar will look like and which pages will appear when the TAB is switched.

{
  "routes": [
    {
      "path": "/",
      "source": "pages/Home/index"
    }
  ],
  "tabBar": {
    "textColor": "#999",
    "selectedColor": "#666",
    "backgroundColor": "#f8f8f8",
    "items": [
      {
        "text": "home",
        "pageName": "/",
        "icon": "https://gw.alicdn.com/tfs/TB1ypSMTcfpK1RjSZFOXXa6nFXa-144-144.png",
        "activeIcon": "https://gw.alicdn.com/tfs/TB1NBiCTgHqK1RjSZFPXXcwapXa-144-144.png"
      }
    ]
  }
}
Copy the code
TabBar configuration items are as follows:
attribute type If required describe
textColor HexColor no Text color
selectedColor HexColor no Select the text color
backgroundColor HexColor no The background color
items Array is Per-tab configuration
TAB item Configuration items are as follows:
attribute type mandatory describe
pageName String is Set the page path toroutesIs configured in the
text String is The text displayed on the TAB item
icon String no Path of unselected ICONS
activeIcon String no Select the path of the status icon

Routing hop

You can obtain a route instance from history.

import { history } from 'rax-app'; Console.log (history.action); // Get the action for the history jump. // Get the location object, including pathName, search, and hash console.log(history.location); History. push('/home'); // For route replacement history.replace('/home'); // To jump to the previous route history.goback ();Copy the code

More history apis

Obtaining Route Parameters

Route parameters can be obtained by getSearchParams. Assuming that the current URL is https://example.com?foo=bar, the parsing query parameters are as follows:

// src/components/Example
import { getSearchParams } from 'rax-app';

function Example() {
  const searchParams = getSearchParams()
  // console.log(searchParams); => { foo: 'bar' }
}
Copy the code

Q&A

HashHistory and BrowserHistory

Front-end routing is usually implemented in two ways: HashHistory and BrowserHistory, both with a # to indicate that HashHistory is being used. Advantages and disadvantages of these two methods:

Features \ Scheme HashRouter BrowserRouter
aesthetic No, there’s a sign good
Ease of use simple Medium, requires server cooperation
Rely on the server Do not rely on Rely on
Conflicts with the anchor function conflict Don’t conflict
compatibility IE8 IE10

Developers can choose the corresponding solution according to their actual situation.

How do I use BrowserRouter

For local development, just add the following configuration to SRC /app. TSX:

import { runApp } from 'rax-app';

const appConfig = {
  router: {
+   type: 'browser',
  }
};

runApp(appConfig);
Copy the code

Server support is required when running online, otherwise the 404 refresh problem will occur. For details, please refer to the community documentation:

  • About the React-Router browserHistory mode
  • React-router HashRouter & BrowserRouter

Static resource usage

Let static resources not be built

You can add static resources to public folders that don’t need to be built through WebPack.

If you put the file in a public folder, WebPack will not process it. Instead, it will be copied to the build folder. To reference resources in the public folder, use a special variable named process.env.public_URL, which changes according to the project’s publicPath:

Render () {// Note: this is an escape hatch and should be used with caution! Return <img SRC ={process.env.public_URL + '/img/logo.png'} />; }Copy the code

Note the drawbacks of this approach:

  • publicNone of the files in the folder will be post-processed or compressed
  • The missing file is not called at compile time and can cause a 404 error for the user
  • The filename of the build product does not contain the content hash, so you need to add query parameters or rename them each time they are changed (to clear the browser cache).

In general, we recommend that you import resources in JavaScript files. This mechanism provides a number of benefits:

  • Scripts and styles are compressed and packaged together to avoid additional network requests
  • Missing files will result in a compilation error instead of giving the user a 404 error
  • The filename of the build product contains a hash of the content, so you don’t have to worry about browsers caching older versions

When to use public folders

In general we recommend importing stylesheets, images, and fonts from JavaScript. Public folders can be used as workarounds for a number of unusual situations:

  • You need files with specific names in the build output, for examplemanifest.json
  • You have thousands of images and need to dynamically refer to their paths
  • You want to include a small script that doesn’t need to go through build logic in addition to the package code
  • In a project, certain libraries may not be compatible with WebPack, and you have no choice but to put them inpublicThe introduction of

Code Quality Assurance

To ensure code quality, we recommend using lint-related tools for code inspection, and to reduce the cost of using regular Lint tools, we encapsulate @IceWorks/Spec as an NPM package, keeping the basic ESLint rules in line with the Alibaba front-end specifications.

Install dependencies

$ npm i --save-dev @iceworks/spec eslint stylelint @commitlint/cli
Copy the code

Importing a Configuration File

eslint

JavaScript project:

// .eslintrc.js
const { getESLintConfig } = require('@iceworks/spec');
module.exports = getESLintConfig('rax');
Copy the code

TypeScript project:

// .eslintrc.js
const { getESLintConfig } = require('@iceworks/spec');
module.exports = getESLintConfig('rax-ts');
Copy the code

stylint

Stylelint is used to detect the style of the style code. A new configuration file named.stylelintr.js is created.

// .stylelintrc.js
const { getStylelintConfig } = require('@iceworks/spec');
module.exports = getStylelintConfig('rax');
Copy the code

commitlint

Commitlintrc.js (commitlintrc.js) commitlintrc.js (commitlintrc.js)

// .commitlintrc.js
const { getCommitlintConfig } = require('@iceworks/spec');
module.exports = getCommitlintConfig('rax');
Copy the code

Configuring the Command line

Using the NPM scripts configuration command:

// package.json
"scripts": {
  "lint": "npm run eslint && npm run stylelint",
  "eslint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./",
  "stylelint": "stylelint **/*.{css,scss,less}"
}
Copy the code

This allows you to run the Lint task via NPM Run Lint.

Tools to ensure

It is recommended to use VS Code for development and debugging, and it is recommended to install the Iceworks and ESLint VS Code plug-ins. Lint errors can be reported in real time during development debugging after the VS Code plugin is installed:

Invalid Typescript problem

If the js project ESLint problem can be reported in real time and the TS project is invalid, open VS Code Setting and enter ‘ESLint validate’ in the search box.

The migration

Migration from a custom configuration

  1. Remove all types of ESLint Plugin Config and Parser from your project.
  2. Install @ iceworks/spec.
  3. Refer to the above documentation to modify the Lint configuration.

Note: Be sure to clear ESLint’s plugin config and Parser packages before the project.

The code of cutting

Code Splitting is the ability to split Code into different bundles for on-demand or parallel loading. Code slicing can be used to get smaller bundles and control resource load priorities, which, when used properly, can significantly reduce the first screen resource load time.

dynamic import

With import(), WebPack will code cut the imported resource at compile time, meaning that the corresponding resource will be loaded only when the runtime logic is executed to the import() call point. This function returns a Promise and the developer can retrieve the referenced module resource in a chained callback.

Example:

import { isWeb } from '@uni/env'; if (isWeb) { import('./fetch').then(fetch => { fetch('m.taobao.com'); }). Catch (err => {console.error(' Module introduction failed! '); }); }Copy the code

rax-use-import

A functional component itself cannot be an asynchronous function, so when import() is used to dynamically load a child component, there is an asynchronous view update process. To simplify the corresponding scenario, we provide rax-use-import the ability to quickly load a child component dynamically in a function component using synchronous writing.

Example:

import { createElement } from 'rax'; +import useImport from 'rax-use-import'; export default function App() { + const [Bar, error] = useImport(() => import(/* webpackChunkName: "bar" */ './Bar')); if (error) { return <p>error</p>; } else if (Bar) { return <Bar /> } else { return <p>loading</p>; }}Copy the code

Arousing client

Call end in the Web page

When our Web pages are accessed by users outside the App, we want to bring users into the App in certain scenarios or when certain functions are not supported outside the App to provide a better access experience. To do this we need to invoke the Web call capability. In Both Android and iOS, there are a variety of call modes to enable Web pages to evoke apps. Starlink.js wraps these call modes, enabling Web pages to quickly and easily connect to call capabilities.

Introduce Starlink JS SDK

In your HTML code, introduce the Starlink SDK like this, where ${star_id} is the star_id you applied for on the platform, and a star_id corresponds to a “customs” platform call policy configuration.

In Rax App, you can configure the scripts field in app.json:

// src/app.json
{
  "routes": [],
  "scripts": [
+    "<script src="//assets.taobao.com/app/starlink/core/index.js" id="${star_id}"></script>"
  ]
}
Copy the code

Configure the page call policy

The default SDK is ready to use the call, but sometimes we need to do some business customization.

Perform call configuration in the “Customs” platform

You can configure the call policy on the Custom platform, but if the custom platform configuration is not suitable for your special customization needs, see “Calls via JS API” and “Calls via DOM properties”.

Call through the JS API
$slk.callApp(config)
Copy the code

Starlink JS globally exposes a $SLK object in the Web environment. You can call it by calling $slk.callapp (config). If you do not pass the Config object, you will default to call it using the configuration returned by the customs platform.

Config Parameter description:

Parameter names If required type An optional value Parameters that
targetUrl no String Page URL Indicates the page that needs to be opened in the end of the call. The default value is returned by the server
clipboardCnt no String Any text If clipboardCnt is set, the contents of clipboardCnt will be written to the clipboard. If clipboardCnt is not set, the clipboard will not be written.Call taobao App’s special logicIf you set clipboardCnt when invoking Taobao, the original default clipboard content will be overwritten. If you do not set clipboardCnt, the default Clipboard configuration of Taobao will be used.
timeout no Number The unit is milliseconds (ms). The waiting time after the call is initiated. For example, if 1000 is set, it indicates that the call fails to be triggered 1000ms after the call is initiated
extraProtocolParams no Object { bc_fl_src: ‘xxx’ } Specifies the parameters that need to be transparently transmitted to the scheme protocol of the call end when the call end is called.
TargetApp (not recommended to configure this value manually) no String Taobao ‘, ‘ltao’, ‘eleme’ Represents the App to be aroused by the call, overriding the target App configuration on the server
The call is configured by setting DOM properties

You can also add A data-callApp parameter to the dom node of the page (currently only A link is supported), so that clicking on the A link will invoke the url of the page directly in the end (not if the href of the A link is not A link).

Description of the data-callapp parameter:

The parameter value meaning
None (default) Click to pull up the App and target page configured by the server star_id
Config (JSON object) Use config JSON to control the specific call configuration

Config JSON Configuration field description:

The field names Example field Values meaning
targetUrl '? k1=v1&k2=v2' / 'https://pageurl.com/xxxx' If the value is'? k1=v1&k2=v2'Is in the format of. This parameter overwrites the url of the current page with the same name'https://pageurl.com/xxxx', indicating that the page of this URL is opened in the end
TargetApp (not recommended to configure this value manually) ‘taobao’ / ‘ltao’ / ‘eleme’ Specify the app to invoke

Data – callapp example:

The default

<a data-callapp href="//xxx.taobao.com/xxxxx"></a> <! -- Click A link to open xxx.taobao.com/xxxxx -->Copy the code

Custom call configuration

<a data-callapp='{"targetUrl":"https://pageurl.com/xxxx", "targetApp": "taobao"}'></a> <! -- Click link A to open https://pageurl.com/xxxx in Taobao -->Copy the code
Global call function

The user can configure the current star_id on the “Customs” platform to enable the “global call function”. At this time, the A link on the page will become the call when clicked, and the effect is to try to open the page of the current A link in the end (at the same time, the current browser will jump to the address of the A link).

Note: If you want to close global calls for some links, you can add ignore-callapp=”true” to the A link

<a ignore-callapp="true" ... </a> <! -- Clicking A link does not trigger the global call -->Copy the code

Inserts business custom logic during calls

Starlink.js also allows businesses to do some custom business logic during the call execution. Businesses can execute their own business logic during the call execution by binding events.

Window. $slk.on('evoke', fn) // customize logic for business in fnCopy the code

Currently supported events and their meanings:

“Evoke” — to evoke

For more information, see the Starlink JS SDK Official Documentation.

After the speech

I am just familiar with the official website document content, there are many, many places not enough, the follow-up plan:

  • rightRact websiteStudy and read in detail, and take your own study notes.
    • General official net small case hand dozen again, record the problem of actual operation process and solve.
  • Do a Rax project (joking that there is no good free practical project on Rax online, prepare to encapsulate Rax framework according to Ract project).

Online to ask the big guy to teach the learning path! Have deficiency or perfect place, hope to put forward!

Hey hey