What is across the
write once, run everywhere
As this phrase describes, writing once and running everywhere is the essence of cross-side. Because our current scenario is really too much, such as android, ios, PC, small programs, or even smart watches, car TV, etc., when a few scenes are very similar, we want to be able to use the least cost of development to achieve the best effect, rather than every client needs a set of separate human for maintenance, this time, Cross-end technology was born. In today’s cross-end schemes, such as react Native, Flutter and electron, are there any common features among them, and whether we can find the essence of them? This paper wants to talk about this.
Various cross-end implementation schemes
H5 hybrid scheme
In fact, the browser is a cross-platform implementation, because you can just type in the url and open your web page on any browser. So, if we embed the browser in the APP, and then hide the address bar and other contents, can we embed our webpage in the native APP? And the browser that’s embedded in the app, we call it a WebView, so as long as one side supports webView, it can use this scheme to cross ends. It’s also the least expensive way to develop, because it’s really just writing a front end interface that’s no different from what we’re doing on a regular web page.
Frame layer + native rendering
What does this mean? The most typical example is react-native. It uses the same syntax as React. In fact, it is react. This is our framework layer. React is different from React in that it requires the use of native capabilities for rendering. In other words, all our components will be rendered as native components, which can bring better user experience.
Frame layer + self rendering engine
The difference is that instead of directly borrowing native capabilities to render components, it leverages lower-level rendering capabilities to render components itself. Obviously, this method will be shorter than the link of the above scheme, so the performance will be better, and at the same time in ensuring the consistency of multi-terminal rendering will be more reliable than the previous scheme. A good example of this type of framework is the flutter.
Alternative across the
As we all know, one thing has become very popular in recent years: applets. This is like opening up their app ecosystem to outside developers, which is a product of The Times. Then the problem comes, Tencent byte Baidu these three manufacturers who belong to the competition relationship certainly can’t customize a set of specifications together, so there are byte applets, Baidu applets, wechat applets. Although we are copied according to the wechat small program, but the implementation is still slightly different, so it is necessary for developers to develop a set of different small programs, which is obviously very time-consuming. This is why frameworks like Taro (Taro) have come up. Taro is a framework that allows you to use a single set of code to develop small programs that can be adapted to a variety of applications. This is also an alternative end, and its implementation is quite interesting, which we will discuss in more detail later.
React-native implementation scheme
Three threads of Rn
Rn currently consists mainly of three threads
-
native thread
-
js thread
-
shadow thread
native thread
This thread is responsible for native rendering and calling native capabilities.
js thread
The JS thread is used to interpret and execute our JS code. In most cases, react Native uses the JavaScriptCore (JSC) JAVASCRIPT engine. When debugging with Chrome, all JAVASCRIPT code runs in Chrome and communicates with the original WebSocket code. The running environment at this point is V8.
shadow thread
One of the most important steps to rendering into the interface is layout. We need to know where each component should be rendered. This is done with Yoga, a cross-platform layout engine based on Flexbox. The Shadow Thread will maintain a Shadow Tree to calculate the actual layout of our components on the Native page, and then inform the Native Thread to render the UI through the Bridge.
Initialization Process
-
Native Starts a native interface. For example, Android will start a new activity to host RN and perform initialization operations.
-
Load the JAVASCRIPT engine and run our JAVASCRIPT code. The react startup process is very similar.
-
The js thread notifies the shadow thread.
-
Shadow Threads calculate the layout and inform native threads to create native components.
-
Native renders native components on the interface and presents them to the user.
Update process
For example, when a user clicks a button on the screen, triggering a click event, the interface needs to be updated accordingly.
-
Native gets the click event and passes it to the JS thread
-
The js thread handles the react code accordingly, such as the onClick function, which triggers setState.
-
Like the React update process, setState is triggered and diff is performed to find the node that needs to be updated
-
Notify the shadow thread
-
After the shadow Thread calculates the layout, it notifies the Native Thread to perform the actual rendering.
The characteristics of
All of the above notifications are implemented through bridge, which implements C++ itself, like a bridge that links modules together. The communication is an asynchronous process. The advantage of this is that there is no blocking relationship between native threads. For example, native threads will not block rendering due to JS threads, which gives users a good experience. However, there is an obvious problem with this asynchrony: poor experience in some time-sensitive scenarios. For example, when you scroll a long list quickly or you need to do some following animations, the whole process looks like this:
-
The Native thread listens to the scrolling event and sends a message to notify the JS thread
-
Js thread processes rolling events. If state needs to be modified, it needs to go through a layer of JS diff to get the node that needs to be updated
-
Js Thread notifies shadow Thread
-
The Shadow Thread informs native rendering
The whole process is actually asynchronous, which can lead to a blank screen and stuttering when swiping fast.
Look at the essence in rn
So now that we know how Rn is implemented across, we can explore what it’s essentially doing. First, the span can be divided into logical span and rendering span. Logical cross-ends are usually implemented through VMS. For example, with the V8 engine, we can run our JS code on various platforms to achieve logical cross-ends.
The second problem is the cross-end of rendering. We abstract the implementation of business code into the development layer. For example, the React code written in React-Native belongs to the development layer, and the specific end to be rendered is called the rendering layer. As a developer, I must know what I want the UI to look like, but I don’t have the ability to render it, so when I declare a component, all WE need to think about is how to tell the render layer what I want.
Like this relationship, the most intuitive way is that I can implement a communication method, in the development layer to inform each system, and then each system itself to call the corresponding API to achieve the final rendering.
function render() {
if(A) {
message.sendA('render', { type: 'View'})}if(B) {
message.sendB('render', { type: 'View'})}if(C) {
message.sendC('render', { type: 'View'}}})Copy the code
For example, I can tell the corresponding side to render the View component based on the platform. This part of the work is what the cross-end framework needs to help us do, it can put this step into the JS layer, it can put this step into the c++ layer. We should try to put this part of the work as low as possible, that is, we can implement a layer of encapsulation for each platform API, the upper layer is only responsible for calling the encapsulated API, and then this layer of encapsulation layer to call the real API. Because this allows more logic to be reused, otherwise we would need to write three different methods to render components on A\B\C, as we did in the JS layer to send messages to different platforms. At the end of the day, though, there must be a place to call a concrete implementation based on the platform, which is this
There is a place to judge the system, notify the corresponding end by some means of communication, and finally execute the real method. In my opinion, all cross-related operations actually do these things.
For example, in hybrid cross-end solutions, webView acts as a bridge layer. CreateElement and appendChild are apis that give us encapsulated cross-platform apis. Where does the underlying layer end up? The details of how to render into the interface have been blacked out. So it’s very easy for us to do cross-side development with these apis, write a web page, and wherever we can load a WebView, our code can run on it.
For example, the Idea of Flutter is to develop a self-rendering engine to cross ends. Is this the same idea as another browser? But the difference is that Flutter is a very new thing, and WebView has to follow a lot of W3C specifications and carry a lot of historical baggage. Flutter doesn’t have any historical baggage, so it can do things better, faster, and more in terms of architecture and design.
What are the current problems with cross-ends
How to do it better
For cross-ends, it is important to mask end-specific details, such as how specific apis are handled, and how to ensure that rendering details are consistent across all ends. If a cross-end framework can keep developers’ code free of isIos, isAndroid, and weird hacks for all kinds of weird renders. Then I think it’s definitely a really successful framework. Unfortunately, as far as I’m concerned, I’ve written H5, RN, and applets, and they haven’t really done that, so there’s all sorts of weird code going on. And this problem is actually very difficult to solve, because the difference between each end is relatively large, so it is difficult to completely shield these details.
The most classic is the grinding vertical center problem in H5, I believe that as long as the development of mobile pages will encounter, I do not need to say more.
How to do it faster
Why are there so many solutions when everyone is essentially doing the same thing? Because you want faster and better performance. As we mentioned earlier, RN’s architecture has led to its unsatisfactory performance in some cases. Rn has noticed this problem itself, so it has adjusted its architecture to solve this problem. Flutter was also created in pursuit of faster performance, so everyone wanted to chase the ultimate performance in various ways.
Applets cross – end
Ok, said so much, for the cross-end part of the content actually I want to say that has said almost, remember the cross-end implementation scheme in the cross-small program scheme? The reason why it is an unusual crossover is that it has no actual crossover, but only incompatibilities between applets’ syntax. But it is really a cross-end solution because it conforms to write once, Run Everything. Let’s first introduce the background of applets
What is a applets
Applets are a capability that app vendors open to the outside world. Through the framework provided by the manufacturer, you can run your own small program in their APP and develop your own business with the help of the traffic of each major APP. At the same time, as a manufacturer, if it can attract more people to join the army of developers, it can also bring more traffic to the APP, which can be regarded as a win-win business. So how is the final page rendered in the app? In fact, it is still through WebView, but some native components will be embedded in it to provide better user experience. For example, the video component is not H5 video, but native video.
What is applets cross – end
So at this point, we can talk a little bit about the applet side. As for cross-application, the core content is not cross-application in the real sense. Although the cross-application is also cross-application, for example, a piece of code can run on Android and Ios, but in fact, it is very similar to hybrid cross-application. What we want to say here is that there are many small programs on the market now: byte small program, Baidu small program, wechat small program, Alipay small program and so on. Although their DSLS are very similar, they are still different, which means that if I want to run my business across multiple apps, do I need to maintain multiple sets of code that are very similar? Can I run all kinds of small programs through one set of code?
How to do
Is it the same thing to want one set of code to run on more than one small program, or the same thing to want one set of code to run on more than one end? Let’s go back to this picture
In the previous cross-terminal sense, platform A, B, C may represent Android, Apple or PC, so can it also represent byte applets, Baidu applets, wechat applets? Let’s copy rn’s example. In the development layer, we’ll still use the React syntax and then write the renderer based on the corresponding applet. Then we can take the VDOM and pass it to the renderer so that the renderer can actually render it into the interface. The process looks like this:
After going through the react process in the development layer, the final result is given to each applet, and then the applet diff, communication and other processes are performed to render the content to the interface.
Typical examples of this approach are Remax and TarO3. They claim to use real React to develop small programs, but there is nothing wrong with it, because react is really the whole thing, and react is no different.
What’s wrong with the React diff? It’s obvious that there’s a double diff. If we were able to connect directly to the render SDK of a small application, there wouldn’t be a need to double diff. The root cause of this problem is that the DSL of applets is rubbish. People want to use a better way to develop applets, so they came up with the idea of using React to write applets.
React knows what it needs, but it doesn’t have the ability to render to the interface, so it needs to use small programs as a rendering layer to render to the real interface. React (vue) React (vue) React (vue) React (vue) React
Another kind of rough cross end
All of these cross-ends are implemented in some architectural way, so if we think about it rudely, can I just compile a set of code to a different platform? For example, I compiled JS code into Java code, object-C code, in fact, personal feeling is not bad, but because these differences are too big, so when writing JS code, it may need very strong constraints, normative, restrict developers in a certain area, in order to compile the past. That is to say, from JS to Java is actually a high degree of freedom to low degree of freedom of a process, certainly can not be completely one-to-one corresponding, and because of the development mode, syntax is completely different, so it is difficult to compile js to ios and Android by way of compilation, but for small programs, Trying to compile JSX to template seems like a viable solution, and in fact, taro1/2 does exactly that. However, from JSX to template is also a high degree of freedom from low process, so I feel that there is no perfect way to compile all syntax to template…
Vdom implications for cross-ends
When it comes to cross-ends, virtual DOM may be the first thing that comes to mind for many people, because it is an abstraction of UI and separated from the platform, so many people may think that virtual DOM and cross-platform are already bound together. But not personally. What is the nature of cross-platform we talked about earlier? The development layer knows what it wants and then tells the rendering layer what it wants. It’s as simple as that. For React-Native, does it use the virtual DOM to determine which nodes need to be updated? In fact, it is not. A virtual DOM alone is not enough to obtain this information, and diff must be added. Therefore, virtual DOM + Diff obtains the information of what it wants, and then tells native to update the real node through communication.
Therefore, virtual DOM only plays the role of a method to get what we want through virtual DOM + diff. In other words, we can also get what we want through other methods. For some frameworks without virtual DOM, such as Baidu’s SAN, it can also be cross-platform. We don’t care how it’s implemented internally, but if it calls createElement at some point during the update phase, it must know what it wants. Corresponding to the content on the upper end, it can tell Native and render something by some means at this time. So, when we get the information about what we want through other means, we can tell Native to render the actual content. So what are the advantages of VDOM? Personally, there are mainly the following two:
-
Create a new era of JSX, functional programming ideas
-
Strong expressive power. The ability to use template for more optimization information and support JSX
First of all, JSX, I think, opens up a new era in which we can write UI in a functional way. Who would have thought that a cutout could write UI in this way?
Second, we know that even though Vue uses Template as a DSL, we can actually write JSX as well, and JSX offers more flexibility than Template. The support for both Template and JSX is due to the existence of VDOM, and there is no way for VUE to support JSX syntax without introducing VDOM, or to support real JSX.
conclusion
So, crosstalk is basically: I know what I want + I tell someone what I want. The essence of them is very simple, but the details are very difficult, for a variety of currently on the market at the same time across the frame, also need to you according to your own project to weigh the pros and cons to choose a best solution, after all, there is no a framework can say that I was able to easing the bit in all other framework, suits own is the best.