introduce
Beeshell is a basic component library for React Native applications. Based on version 0.53.3, Beeshell provides a full set of high-quality components out of the box, including JavaScript components and composite components (including Native code). It involves front-end (FE), iOS, and Android technologies, giving consideration to versatility and customization, supporting custom themes, and developing and serving enterprise-level mobile applications. Now available on GitHub at github.com/meituan/bee…
Up to date, the components of beeshell have Meituan take-out bees are widely used in the App, mobile application and has been going on for more than a year of time, through the various business scenarios, operating systems, models of practical test, has the very good stability, security and ease of use, so we will be open source, in order to play a bigger application value.
features
- Consistency and customization of UI styles.
- Versatility. Mainly use JS to achieve, to ensure cross-platform versatility.
- Customization. We split the components in a relatively fine granularity, rely on each other through inheritance, and gradually enhance the function, which provides the possibility of inheritance extension and personalized customization at any level.
- Native feature support. The composite components in the component library contain Native code to support Native functions such as image selection and location.
- Feature rich. It provides not only components, but also basic tools, animations, and UI specifications.
- Complete documentation and use examples.
contrast
Before open source, we have conducted a survey of the industry has been open source component library, here we mainly compare the advantages and disadvantages of Beeshell and other component libraries, to provide reference for you to choose a component library. At present, there are many open source component libraries in the industry. Here, we only select the component libraries with the number of Github Star more than 5000, and conduct comparative analysis from the five dimensions of component number, universality, customization, whether it contains native functions and the degree of document perfection
Component library | Number of components | generality | customized | Whether native functionality is included | Degree of document perfection |
---|---|---|---|---|---|
react-native-elements | 16 | Strong, provides a consistent set of UI controls | Weak and may need to be overridden for customization | no | high |
NativeBase | 28 | Strong, provides a consistent set of UI controls | In, subject variables are supported | is | high |
ant-design-mobile | 41 | Strong, provides a consistent set of UI controls | In, some can support customization requirements | is | low |
beeshell | 25 | Strong, provides a consistent set of UI controls | Strong, not only supports topic variables, but also supports custom extensions using inheritance | is | high |
By comparison, Beeshell is only slightly inferior in terms of the number of components, and is otherwise consistent or superior to other projects. Because Beeshell had a good system architecture, it was only a matter of time before the number of components was abundant, and our team already had a detailed plan to address the lack of quantity.
The system design
System design is the active process of transforming a practical problem into a corresponding solution. It is the description of the solution. In the general software engineering model, the first step after the completion of requirements analysis is system design. The final stability and ease of use of a project also largely depends on the system design step.
The Beeshell component library is designed to build mobile applications more quickly, provide basic technical support for business development, and greatly improve developer efficiency. However, in the face of different business parties, different functional requirements, different UI specifications and interaction modes, how to effectively take account of all requirements? This put forward higher requirements for system design, the following level of abstraction to reduce the way to detail beeshell system design.
The framework design
In recent years, React Native has provided a new option for mobile development. React Native is more efficient than Native development and has better performance than HTML5 and Hybrid, which makes it stand out from the crowd. As a result, more and more developers start to learn and use React Native.
Based on React Native, the Beeshell component library interacts with iOS and Android platforms at the system level through React Native. It provides a developer-friendly unified interface to bridge platform differences and provide service support for users to develop business functions. Beeshell acts as an intermediary to ensure the stability and ease of use of basic features of mobile applications.
The framework design defines the system boundaries of Beeshell, specifying the boundaries between what is included and what is not. Clear system boundary, we can continue the following analysis, design and other work.
Design principles
Prior to the detailed design of the component library, we proposed several design principles:
- JS implementation takes precedence. Using JS to implement functionality has several benefits: cross-platform versatility, higher development efficiency, and lower learning and use costs.
- Flexible use of inheritance and combination. Inheritance and composition are both effective design techniques for functional reuse and code reuse, and both are basic structures in design patterns. Inheritance allows subclasses to override the implementation details of the parent class. The implementation of the parent class is visible to subclasses. This is commonly referred to as “white box reuse”, which is very effective for component customization. Composition is recommended by React. The React component has a powerful composition model, and the implementation details between the whole class and part class are not cared about. The implementation details between them are invisible, which is commonly referred to as “black box reuse”. Beeshell is also widely used in combination, based on universal components to combine more rich, powerful, personalized functions, to a certain extent, improve the ability of customization of Beeshell.
- Low coupling, high cohesion. A Beeshell component is essentially a React component. React components communicate with each other mainly through Props, which belongs to data coupling. Compared with other coupling methods such as content coupling and control coupling, data coupling is the one with the lowest coupling degree. Low coupling of beeshell components is natural; In order to achieve high cohesion, there are certain requirements on the coding implementation of components. We promote interactive cohesion and sequential cohesion with high cohesion. Using a single data source, each element operates on the same data structure and achieves interactive cohesion. Using immutable data updates, where the output of one link is the input of the next, pipelining logic is called sequential cohesion.
The project design
On the whole, JS is used as a unified entrance, multi-layer encapsulation hides implementation details, and smooths out the differences between JS and Native, iOS platform and Android platform, which reduces users’ learning and use costs out of the box. Partially based on React Native’s technical characteristics, it is divided into JS component part and composite component part. The development mode of the two parts is “loose coupling”, which enables the Native part to have the ability to replace and change, and improves the flexibility of the component library.
The composite component part can expose the JS interface directly or, if necessary, can be customized wrapped in the JS component part. We try our best to ensure atomicity and simplicity of Native functions, and use JS to achieve unified implementation of any customization requirements. We follow the design principle of JS implementation priority to ensure cross-platform universal features. The following introduces the design of JS component part and composite component part respectively.
JS component part design
The design of a software is divided into three design levels: architecture, code design and executable design. We use a top-down approach to design the JS component parts from the architecture.
There are usually seven styles of software architecture: pipes and filters, object-oriented, implicit requests, hierarchies, knowledge bases, interpreters and process controls.
JS component part uses the hierarchical architecture style, the whole is divided into three layers: basic tools, general components, extension components, from top to bottom universality gradually weakened, gradually enhanced customization, function gradually enhanced, through the layered design, each layer plays its own role, both universality and customization.
- Basic Tools (Common) : The most basic, general section, including JS Utils, animation definitions, UI specifications, and so on.
- General components: the components with similar functions are classified and sorted into a series. Each series is realized by inheritance internally, with layer upon layer dependence and gradual enhancement of functions. This part focuses on generality and does not consider the requirements of customization to ensure the simplicity of the code. At the same time, components are split in fine granularity, which provides good scalability.
- Modules: It is the inheritance, extension and combined application of general components. This part focuses on customization and meets business requirements to the greatest extent with low generality.
The extension component part will provide a large number of customized components. If the requirements are still not met, users can learn from the implementation of the extension component to inherit common components on a certain inheritance hierarchy according to their own business requirements, which fully reflects the ability of customization of Beeshell.
Composite component part design
The React Native component library includes Native features. The Beeshell component library has completed the integration scheme and specification of Native part, and has good development and use experience, and can continuously integrate Native functions.
The composite component part encapsulates the interface with JS to ensure cross-platform. The Native part is mainly divided into Native Bridge and pure Native. Bridge is the encapsulation of React Native and must be implemented in the component library. The pure Native part can be implemented by Pods/Gradle relying on three parties, effectively absorbing and utilizing the technology accumulation of Native development.
Component library implementation
Cross-platform versatility guarantee
React Native provides built-in components that enable javascript implementation. Some of these built-in components are cross-platform universal components, such as View, Text, and TextInput. Others are implemented separately by the two platforms, such as DatePickerIOS and DatePickerAndroid, AlertIOS and ToastAndroid. Of course, there is nothing wrong with cross-platform components. We can focus on the development of business functions. The problem is that these non-cross-platform components bring great trouble to our business function development.
DatePickerIOS for iOS:
DatePickerAndroid component for Android:
Not only are the functional interactions completely different, but the class names and invocation methods are different, which not only does not meet the business requirements, but also has a high cost of learning and using. There are many similar components, how to erase the differences of flat platform, achieve cross-platform? Our solution is to use JS in preference to implement functionality, which is the design principle of our component library.
In view of the above problems, we developed the Datepicker component based on ScrollView, which unified the class name and call method to ensure cross-platform universality.
Datepicker for iOS:
Datepicker for Android:
Datepicker uses JS to fully implement a complete function, but in some cases it is not necessary to implement a complete function. We can use the React Native Platform to implement partial cross-platform processing, such as the TextInput component.
TextInput for iOS:
TextInput for Android:
We can see that the Android Platform does not empty ICONS. In order to erase the differences of the flat Platform and provide better versatility, we developed the Input component to encapsulate and optimize TextInput and use Platform to position the Android Platform to provide the empty function.
The Input component looks like this on Android:
In a word, Beeshell further optimizes the cross-platform versatility, following the principle of JS implementation priority and cooperating with Platform Platform positioning API to provide a better guarantee for the ease of use and versatility of components.
Customization Support
With the rapid development of mobile Internet, various kinds of mobile terminal products emerge and develop continuously, which also makes software knowledge continuously popularized, and the positioning of business side on product functions gradually changes from manufacturer-led to user-led. Product functions are more accurate, personalized, refined, deepening is an inevitable trend, through customized services to meet the requirements of product development has emerged. Different industries, different types of products, functions, characteristics are not the same, with a certain established software products to meet different types of demand, its applicability can be imagined. Customization has a good technical architecture and technical advantages, which can be customized, extensible, integrated and cross-platform. It has a good advantage in the processing of personalized requirements, so we need customization.
To sum up, Beeshell regards customization as its core feature and strives to meet the customization requirements of different products. The following will be elaborated from two aspects of component style customization and function customization.
Style customization
Beeshell’s design specification supports a degree of style customization to meet the diverse visual needs of the business and the brand, including but not limited to visual customization of brand colors, rounded corners, borders, etc.
UI specifications are unified from the beginning of the component library design. We according to the UI specification, unified style variables and placed in the basic tools layer, namely beeshell/common/styles/varibles. Js file, in the React Native applications, the style is common js variables, It is easy to reuse and rewrite operations. React Native provides StyleSheet to reduce the frequency of creating new style objects by creating a StyleSheet that uses ids to reference styles. Use StyleSheet. Create and StyleSheet. Flatten flexibly in the component library’s style variable application to get the style ID and style object.
In the implementation of each group, style variables from the underlying tooling layer are introduced beforehand, and uniform variable objects are used instead of defining them in components, thus ensuring consistency in UI style. At the same time, Beeshell provides an API for resetting style variables, enabling one-touch peels. We recommend that beeshell users define style variables in advance when developing mobile applications. Reset beeshell’s style variable with its own style variable; On the other hand, in the development of business functions, we use our own defined style variables to ensure the consistency of the overall UI.
Function customization
Style customization can be realized from the macro and overall perspective, while function customization needs to be analyzed and realized from the micro and local perspective. In the following, the implementation of the Modal series is used as an example to describe the function customization in detail.
The popover interaction on the mobile terminal is generally simpler than that on the PC terminal. We categorise the components similar to the interaction such as the Modal box, drop-down menu and information prompt into the Modal series and implement it by inheritance. One might ask why inheritance and not composition? As mentioned earlier, the main purpose of composition is code reuse, while the main purpose of inheritance is extension. Given that popover interactions have many customization possibilities, we chose inheritance for better extensibility.
First of all, we look at the realization of several components of the effect diagram, the Modal series first have an intuitive understanding.
Modal components:
Masks, pop-up containers, and Fade animations are provided, with the pop-up content portion completely customized by the user. This component is extremely versatile and does not have any custom functions. It should be noted here that the animation part is implemented independently, providing two subclasses of FadeAnimated and SlideAnimated, using the policy mode to integrate with the Modal family, and the Modal component integrates FadeAnimated by default.
ConfirmModal components:
Inheriting Modal component, the popup content has made a certain degree of customization expansion, support title, confirm button, cancel button and custom body part of the function, generality weakened, customization enhanced.
SlideModal components:
Inheriting Modal components, rewrites animation and popup containers, instantiates SlideAnimated objects during initialization, completes pull-up and pull-down drawings, and supports custom popup positions.
PageModal components:
Inheriting SlideModal component, custom extension of pop-up content, support for title, confirm button, cancel button and custom body function, reduced commonality and increased customization.
CheckboxModal components:
CheckboxModal component is realized by PageModal and Checkbox components using combination, which combines more powerful functions based on general components and follows the design principle of inheritance and flexible use of combination.
Through the above part, we already have an intuitive understanding of the Modal family, and then let’s look at the Modal family class diagram and layering:
The animation part is implemented in the Base Tool (Common); The Modal component aggregates FadeAnimated in the Generic components, and because SlideModal and ConfirmModal are more generic, it is also implemented in this section; CheckboxModal is more customized and is categorized into modules. By layering in this way, the three layers play their respective roles, making the hierarchy of the component library clearer, not only realizing customization, but also ensuring the simplicity and maintainability of the common parts.
Complex Case processing
Handle asynchronous rendering recursively
The JS thread and UI thread of React Native application are two threads, which is different from the implementation of the browser using the same thread. Therefore, we can see that the API provided by React Native to operate UI elements is called through callback functions.
React doesn’t usually require direct UI manipulation, but some components do require complex UI manipulation, such as the Scrollerpicker component, which is implemented entirely with JS:
We need to accurately calculate the height of the container and each element to get the correct index of the currently selected item in the data model (array). The problem is that componentDidMount does not get the height of the correct container for the life cycle after the component is rendered, and setTimeout has some issues with how long the delay should be set. We chose to use recursive resolution, executing setTimeout as many times as possible.
Interactive recursion is used, repeated until a valid element size is reached.
UI size tolerance mechanism
React Native provides users with a style attribute to control the style of elements, and you can manually set the size of relevant UI elements. However, on some Android machines, the size of the element we set is inconsistent with the size information obtained by measure method. After practical tests on a large number of Android machines, we come to the conclusion that there is an error of a few tenths of a pixel.
We rounded up and down the size information obtained by measure method to obtain a threshold range. As long as the size information set manually is within this threshold range, it is considered to be an effective size. This fault-tolerant mechanism is effectively compatible with extreme situations and improves the stability of components.
Fine layout control
The most common requirement when working with Form components is validation, which is usually built into the Form components of component libraries. However, because there are synchronous and asynchronous verification modes, the verification results can be displayed in various styles and locations, which leads to the complexity of the verification function.
Absolute positioning:
The Static positioning:
Custom location
How to effectively balance different needs? We proposed validation implementation independent, in the use of the Form components of the parent component, using CVD to define, configuration validation rules, and check the results output to a unified data structure (a single data source), based on the data structure, we can use them at any time, any place, any style to check information.
Let’s first introduce CVD:
CVD is a layered solution for complex form entry scenarios. It is lightweight, cross-platform and easily extensible. It is built into the Beeshell component library and can be used directly.
CVD divides the entry process of a form control into three layers:
- The Connector converts user input information into the desired data format.
- Validator Verifies formatted data.
- Dependency Handlers handle the dependencies of the current control to other controls.
Each layer carries out immutable data update for a single data source Store, which conforms to interactive cohesion and sequential cohesion with a high degree of cohesion.
Each layer uses functional combination to define the callback function corresponding to key (the unique symbol of the form control), avoiding batch if else, which can effectively reduce the ring complexity of the program.
The following uses the Input component to enter the name as an example, and the code is as follows:
Get user input in onChange, call cvd.flow and get the result from cvd.getStore:
The verification function is implemented independently. The verification information is output to the Store and obtained from the Store when needed. In this way, the style, location, and layout of elements can be more refined and compatible with various customization requirements. Most of the time, only we think, nothing can not do.
test
The end goal of code is twofold. The first is to fulfill requirements, and the second is to improve code quality and maintainability. Testing is designed to improve code quality and maintainability, and is one way to achieve the second goal of your code.
Unit testing
Unit Testing refers to the examination and verification of the smallest testable units in software. In the days of structured programming, unit testing meant function. The Beeshell component library makes full use of unit testing, done by the component’s developer. Research has shown that full regression testing is required whenever changes are made, especially for components that provide basic functionality, and that testing software products early in the life cycle will best ensure efficiency and quality. The later a Bug is discovered, the more expensive it is to fix it, and unit testing is an opportunity to catch bugs early.
The advantages of unit testing are as follows:
- It’s an act of validation. Each function of the program is tested to verify the correctness, which provides a guarantee for the later increase of functions and code reconstruction.
- It’s an act of design. Unit testing allows us to see and think from the caller’s point of view, forcing developers to design programs that are easy to call and testable, reducing coupling to some extent.
- Is the act of writing documentation. Is the best document to show how functions and classes are used.
The Beeshell component library uses Jest as a unit testing tool, with assertions, test coverage tools, out of the box.
Test case design
The core of the test case is the input data. Representative data will be selected as the input data, including normal input, boundary input, and illegal input. The following uses isLeapYear tool function provided in the component library as an example, with the following code:
Jest uses the test function to describe a test case where the toBe side is an assertion.
The function uses external data, which normal input must have, so 2000 and ‘2000’ are both normal input; Not all functions have boundary input and illegal input. To illustrate the use of examples with both types of input, boundary input is the limit of valid input, where 0 and Infinity are boundary input; Illegal input is data outside the normal value range, ‘xx’ and false are illegal input. Unit testing and code writing are “two sides of the same thing.” You should consider all three of these inputs when coding, otherwise the robustness of the code will be in question.
The tests described above are designed for the functionality of the program, known as “black box tests.” Unit testing also requires the design of test data from another Angle, namely the design of test cases against the logical structure of the program, known as “white box testing.”
The isLeapYear function is used as the following code:
So here we have an if else statement, and if we just provide an input of 2000, we’ll only test the if statement, not the else statement. Although white box testing is not necessary when black box testing is sufficient, “sufficient” is an ideal state, and difficulty in measuring the integrity of the test is a major disadvantage of black box testing. The white-box test has the advantage of being easy to measure the integrity of the test, and has excellent complementarity between the two. For example, the statement coverage is counted after the completion of the functional test. If the statement coverage is not completed, it is likely that the function points corresponding to the uncovered statements are not tested.
White-box testing is also a common requirement. Jest has a built-in test coverage tool. You can print a report of unit test coverage by adding the –coverage parameter directly to the command.
You can see that every line of code has 100% Coverage, which ensures the stability of the function to a large extent.
UI Automation testing
Snapshot Testing is a useful tool to ensure that the UI of a component library is not accidentally changed. A typical mobile App snapshot test case process involves rendering UI components, then taking screenshots, and finally comparing them to reference images independent of the test store. When you test a component in snapshots using Jest, the first time you test a component in Beeshell, you create a snapshots folder in the test directory and save the snapshots in that folder. The snapshot result file is named < component name >.js.snap and contains a TREE of UI components in a certain state.
The following uses the Button component snapshot test as an example:
The snapshot result is displayed after the command is executed:
Static analysis
Another development activity that is often associated with unit testing is Static analysis. Static analysis is the perusing of software source code, looking for errors or gathering metrics, without the need to compile and execute the code.
Static analysis effect is good and fast, can find 30%~70% of the code problems, can be checked in a few minutes, low cost, high income. Beeshell uses SonarQube for static code checking.
SonarQube is an open source code quality management system that supports more than 25 languages. It can be integrated with Eclipse, VSCode and other tools by using plug-in mechanism to fully automate the analysis and management of code quality.
SonarQube provides comprehensive code analysis based on Reliability, Security, Maintainability, Coverage, and communications. Ensure code Quality by setting Quality Gates.
The analysis results of the Beeshell component library are summarized as follows:
The reliability reaches level A, the highest level, indicating that there is no Bug:
Security level A, the highest level, means no vulnerabilities:
The test coverage averaged more than 70%
Develop and use consistency
The beeshell component library is downloaded and used as an NPM package. After the download is successful, it is placed in the node_modules directory of the root directory of the project, and then used by importing beeshell components into the project by importing modules.
So how do we develop component libraries? How to ensure the consistency of the development and use experience of the component library?
First, we need a Demo project. This project is the Development environment for the Beeshell component library. It is a React Native application. We then download and install Beeshell as a dependency on the demo project.
Now, the problem becomes how to synchronize beeshell in the node_modules directory with the local beeshell source code.
npm link
We know that we can use NPM Link to develop NPM packages as follows:
However, when we set up the soft link and run the package command, an error was reported. Expected path ‘/ XXX/XXX /index.js’ to be relative to one of project roots
Our front-end development usually uses Webpack as a packaging tool, while React Native uses Metro. We need to analyze Metro to locate problems.
Webpack vs Metro
After Metro source code analysis, we found that Metro’s packaging scheme is quite different from Webpack. Webpack is based on the entry file, namely the entry attribute in the configuration, recursive parsing dependency, Build a dependency graph Whereas Metro crawls all files under a particular path to build a dependency graph.
Metro’s specific path defaults to the path in the package life, and the directory below node_modules, so we can locate the problem:
Metro found the global beeshell through a soft link while crawling the file, but did not continue to determine whether the global Beeshell has a soft link, so it was unable to crawl the beeshell source section.
Use soft links directly
Run the ln -s command to create a soft link between the beeshell package and the source code of the demo project node_modules:
This method also supports Native part of iOS, Android source development, pay attention to the Android part of the need to call getCanonicalPath method in setting. Gradle to obtain the path after the establishment of soft link.
By testing, finding problems, analyzing source code, locating problems, solving problems and improving the scheme, the beeshell component library development and use experience consistency are completely realized, and the development efficiency of the component library is improved.
future
Our goal is to build Beeshell into a large and comprehensive component library that will not only enrich JS components, but also enhance composite components to support more low-level functionality. Because we support both full import and on-demand import, users do not need to worry about introducing too many useless components and making packages bulky, affecting development and usage efficiency.
Beeshell currently provides 20+ components and basic tools, based on good architectural design and development experience, which provides a good foundation for us to continuously enrich the component library. Meanwhile, during several years of developing React Native application, we have accumulated more than 50 bases and business components. We will sort out and adjust the accumulated components and migrate them to Beeshell. Because our components are primarily based on our business needs, but limited business scenarios could limit the growth of Beeshell, we made it open source. We hope to enrich the functions of the component library with the help of the community, and try our best to cover all aspects of the functions of mobile applications. We welcome your suggestions and support.
We have planned three phases for component library development:
- The first phase, where we are now, is open source 20+ components, which provide basic functionality.
- In the second stage, we sorted out the components accumulated during several years of React Native application development and opened source 50+ components.
- In the third stage, I investigated the commonly used functions of mobile App, analyzed and sorted them out, and then implemented them in Beeshell and opened source 100+ components.
Open source related
Git address
beeshell
Core contributors
Front end: Xiao Long, Meng Qian
Native: Profound, Yang Chao