The React community has been exploring ways to use the React syntax to develop applets, notably Taro and Nanachi. The main difficulty of developing applets using React syntax is the JSX syntax. JSX is JS in nature and is much more flexible than static applets templates. In this paper, the new way of thinking is in the treatment of the JSX grammar on the new way of thinking, this is a more dynamic processing, compared with the existing schemes, basically won’t limit any JSX writing, let you to really React treatment of small procedures, hope that the new way of thinking can use the React to anyone interested in people inspire the development of the program.

Limitations of existing thinking

Before we introduce the new ideas, let’s take a look at Taro (latest version 1.3) and see how Nanachi handles JSX syntax on the applet side. In short, React code is run on the applet side by converting JSX to its applet equivalent, WXML, at compile time.

For example, the React logic expression:

xx && <Text>Hello</Text>
Copy the code

Will be converted to the equivalent applet wx:if instruction:

<Text wx:if="{{xx}}">Hello</Text>
Copy the code

This approach puts JSX processing primarily at compile time, which relies on information gathering at compile time. For example, it must recognize the logical expression and then do the corresponding WX :if conversion.

What are the problems and limitations of the compile phase? Let’s illustrate the following examples:

class App extends React.Component {
    render () {
        const a = <Text>Hello</Text>
        const b = a

        return (
            <View>
                {b}
            </View>)}}Copy the code

Const a =

Hello
const a =

Hello

This example is not particularly complicated, but the error is reported.

To understand why the code above reported an error, we need to understand the compile phase first. The code is essentially a ‘string’ at compile stage, and the compile stage processing scheme needs to analyze the necessary information from this’ string ‘(via AST, re, etc.) and then do the corresponding equivalent conversion processing.

For the example above, what equivalent treatment is required? B = a =

Hello
, then replace {b} in

{b}
with

Hello
. However, at compile time it’s very difficult to determine the value of B, and some people say you can go back and determine the value of B, but think about it because b is equal to a, you have to determine the value of A, and how do you determine the value of A? Need in the scope chain of b can access to determine a, however a might again by other variable assignment, cycling, once appear, is not a simple assignment during the period of situation, such as the function call, three yuan runtime information, such as to determine trace is failed, if a itself is hung on global object variables, traceability is even more impossible.

So it’s not easy to determine the value of b at compile time.

Let’s look at the error message in the figure above: A is not defined.

Why is a undefined?

Hello
is equivalent to react. createElement(Text, null, ‘Hello’), and the react. createElement method returns a normal JS object of the form

/ / ReactElement object
{
   tag: Text,
   props: null.children: 'Hello'. }Copy the code

So the above code is roughly equivalent to the following when the JS environment is actually running:

class App extends React.Component {
    render () {
        const a = {
            tag: Text,
            props: null.children: 'Hello'. }const b = a

        return {
            tag: View,
            props: null.children: b ... }}}Copy the code


Hello
The JSX fragment is processed specially. A is no longer a normal JS object. Here we see that the A variable is even lost, which exposes a serious problem: The code semantics are broken, that is, because of the compile-time scheme’s special handling of JSX, the code semantics that actually run on the applet are not what you would expect. This is a headache.

New train of thought

Because of the limitations of compile-time schemes like the one above, you often get the “Am I still writing React? This feeling.

React. CreateElement JSX expressions are handled as react. createElement method calls, which are normal JS objects. Eventually render the applet view in some other way. Let’s explain the details of this idea in detail.

Step 1: Give each individual JSX fragment a unique identifier UUID, assuming we have the following code:

const a = <Text uuid="000001">Hello</Text>

const y = <View uuid="000002">
	<Image/>
	<Text/>
</View>
Copy the code

We added uUID attributes to fragments A and Y

Step 2: Escape the React code via Babel into code that small programs can recognize, such as replacing the JSX fragment with the equivalent react. createElement, etc

const a = React.createElement(Text, {
  uuid: "000001"
}, "Hello");
Copy the code

Step 3: Extract each individual JSX fragment and wrap it with a small program template to generate a WXML file

<template name="000001">
	<Text>Hello</Text>
</template>

<template name="000002">
	<View uuid="000002">
		<Image/>
		<Text/>
	</View>
</template>


<! - to take the template - >
<template is="{{uiDes.name}}" data="{{... uiDes}}"/>
Copy the code

Note that the name identifier for each template is the same as the unique identifier UUID for the JSX fragment. Finally, you need to generate a placeholder template at the end:

Step 4: Modify the reactdom.render recursion process (after React 16.x, not recursion), the recursive execution phase, aggregate the UUID attribute of the JSX fragment, generate and return the uiDes data structure.

render the final view.

To illustrate the process, we use the App component example above. First, the JS code will be escaped as:

class App extends React.Component {
	render () {
	    const a = React.createElement(Text, {uuid: "000001"}, "Hello");
	    const b = a
	    
	    return (
	      React.createElement(View, {uuid: "000002"} , b); ) }}Copy the code

Generate WXML file at the same time:

<template name="000001">
	<Text>Hello</Text>
</template>

<template name="000002">
	<View>
		<template is="{{child0001.name}}" data="{{... child0001}}"/>
	</View>
</template>

<! - to take the template - >
<template is="{{uiDes.name}}" data="{{... uiDes}}"/>
Copy the code

Use our custom render to perform reactdom.render (
, parent). In the recursive process of render, in addition to the routine creation of component instances and execution life cycle, additional collection of UUID identifiers of components in the execution process will be performed, and finally the uiDes object will be generated

const uiDes = {
	name: "000002".child0001: {
   	    name: 000001. }... }Copy the code

JSX fragments don’t get any special processing, just a simple react. createElement call. In addition, since the React procedure is purely JS, it is very fast, usually only a few milliseconds. Finally, a uiDes data is output to the applet, which uses the uiDes to render the view.

Now that we’re looking at our previous assignment of const b = a, we won’t have any problems, because A is just an ordinary object. In addition, restrictions on common compile-time schemes such as arbitrary functions returning JSX fragments, dynamically generating JSX fragments, and for loops using JSX fragments can be completely removed, because JSX fragments are just JS objects that you can do anything with, Reactdom.render eventually collects the UUID identifier of all the resulting fragments to generate the uiDes, and the applet renders the final view based on this uiDes data structure.

The processing of JSX fragments is dynamic. You can show any JSX fragment anywhere, in any function, and the final execution result determines which fragment to render. Only the UUID of the executed fragment is written to the uiDes. This is fundamentally different from static identification of compile-time schemas.

conclusion

“Talk is cheap. Show me your code! This is just one idea, right? Or is there already a full implementation?

There is a complete implementation. Alita project uses this idea when dealing with JSX syntax, which is also the reason why Alita can transform the whole React Native project without limiting the writing method. In addition, Alita has made a lot of optimization on this idea. If you’re interested in a concrete implementation of this idea, read up on the Alita source code, which is completely open source at github.com/areslabs/al… .

Of course, you can also build your own React applets based on this idea.