The introduction

Last time, we successfully rendered the React application onto the Canvas. Today we’re a little bit more ambitious, implementing a simple React Native, which we call Extremely Tiny React Native. Our final result is shown in the picture below:

The React code looks like this:

import React from 'react'
import {View, Text} from './react-native'
import {useEffect, useState, useRef} from 'react'

const W = 100
const innerW = 50

function useUpdate() {
  const [_, _update] = useState()
  return () = > _update(Math.random())
}

function App() {
  const x = useRef(0)
  const y = useRef(0)
  const update = useUpdate()
  const animate = () = > {
    setTimeout(() = > {
      if (y.current === 0 && x.current < W - innerW) {
        x.current += 1
      } else if (x.current >= W - innerW && y.current < W - innerW) {
        y.current += 1
      } else if (y.current >= W - innerW && x.current > 0) {
        x.current -= 1
      } else {
        y.current -= 1
      }
      update()
      animate()
    }, 50)
  }
  useEffect(() = > {
    animate()
  }, [])
  return (
    <>
      <Text x={50} y={50} w={W} h={W} r={0} g={0} b={0} a={1} fontSize={16}>
        Tiny React Native
      </Text>
      <View x={50} y={100} w={W} h={W} r={255} g={0} b={0} a={1}>
        <View
          x={x.current}
          y={y.current}
          w={innerW}
          h={innerW}
          r={0}
          g={255}
          b={0}
          a={1}>
          <Text
            x={18}
            y={13}
            w={50}
            h={50}
            r={0}
            g={0}
            b={255}
            a={1}
            fontSize={20}>
            S
          </Text>
        </View>
      </View>
    </>)}Copy the code

Pre-knowledge preparation

The two core knowledge required to implement this Extremely Tiny React Native are:

  • JavaScriptCore
  • React Custom Renderer

React Custom Renderer React Custom Renderer JavaScriptCore

JavaScriptCore

JavaScriptCore (JSCore for short) is a JavaScript (JS for short) execution engine on iOS, which builds a bridge between Objective-C (OC for short) and JS. Let’s take a look at some of its basic uses:

Example 1: Execute JS code:

JSContext *jsCtx = [[JSContext alloc] init];
JSValue *value = [jsCtx evaluateScript:@"function hi(){ return 'hi' }; hi()"];
NSLog(@ "% @", value); // hi
Copy the code

Example 2: JS calls Native methods:

jsCtx[@"log"] = ^ (NSString *msg){
    NSLog(@"js:msg:%@",msg);
};
[jsCtx evaluateScript:@"log('hello,i am js side')"];
Copy the code

As shown above, after we mount a log method on the JSContext object, the method can be called directly in the JS code.

Example 3: JS uses objects in Native:

  1. Start by customizing a protocolJSPersonProtocolInherited fromJSExprotAnd define the properties and methods that need to be exposed to JS:
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
NS_ASSUME_NONNULL_BEGIN

// Define a protocol, which can be understood as an interface
@protocol JSPersonProtocol <JSExport>
- (NSString *)whatYouName;
@end
Copy the code
  1. Create a newPersonObjects, implementation protocols and methods:
// Inherit NSObject to implement JSPersonProtocol
@interface Person : NSObject<JSPersonProtocol>
@property (nonatomic.copy)NSString *name;
- (NSString *)whatYouName;
@end

NS_ASSUME_NONNULL_END
Copy the code
#import "Person.h"

@implementation Person- (NSString *)whatYouName {
    return @"Ayou";
}
@end
Copy the code
  1. Use:
Person *p = [[Person alloc]init];
jsCtx[@"person"] = p;
JSValue *name = [jsCtx evaluateScript:@"person.whatYouName()"];
NSLog(@ "% @",name); // Ayou
Copy the code

Realize the principle of

The React Custom Renderer needs to implement host-specific interfaces such as createInstance and appendChild.

In our Extremely Tiny React Native, these interfaces send MESSAGES in JSON format to Native through JSCore, and the real operation is completed on Native side. The implementation method of message passing has been introduced in example 3 above, here is a simple paste code:

@protocol BridgeProtocol <JSExport>

- (void) send:(NSString *)msg;

@end

@interface Bridge : NSObject<BridgeProtocol>
- (void) send:(NSString *)msg;
@end

NS_ASSUME_NONNULL_END
Copy the code
- (void) send:(NSString *)msg {
     // Serialize the JSON string
    NSError *jsonError;
    id jsonObj = [NSJSONSerialization JSONObjectWithData:[msg dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&jsonError];
    NSString *operation = [jsonObj objectForKey:@"operation"]; . }Copy the code
self.jsContext[@"RNBridge"] = [[Bridge alloc] initWithRootViewController:self];
Copy the code

For example, when calling createInstance, JS will notify Native that the current operation is createView (or createText), which requires the creation of an RNView (or RNText) object:

// RNView
[self.eleDict setObject:[[RNView alloc] init:props] forKey:_id];
// RNText
[self.eleDict setObject:[[RNText alloc] init:props] forKey:_id];
Copy the code

In addition, a unique identifier ID is assigned to the object to facilitate subsequent operations to find the corresponding object. This ID is mounted on the FiberNode stateNode property (this step is implemented for us by React).

When appendChild is called, React passes parent and child (note that parent and child are the aforementioned ids) :

appendChild: function (parent, child) {
  RNBridge.send(JSON.stringify({operation: 'appendChild', parent, child}))
},
Copy the code

The rest of the interfaces can be implemented step by step. See tiny-React-Native for the complete code.

conclusion

Use JavaScriptCore and React Custom Renderer to implement React Native. However, it is still very simple.

  • Support for the style attribute
  • Support the Reload function
  • Support Flexbox layout

Welcome to pay attention to the public account “front-end tour”, let us travel in the front-end ocean together.