• 随着对react native的深入学习和实战积累的开发经验,我已经重新建立了一个新项目react-native-boilerplate-dolphin。此项目是一个rn app的模板项目,可用于学习和二次开发。 github项目地址 ...

    随着对react native的深入学习和实战积累的开发经验,我已经重新建立了一个新项目react-native-boilerplate-dolphin。此项目是一个rn app的模板项目,可用于学习和二次开发。

    github项目地址

    https://github.com/afresh/tadpole

    目录

    1.创建项目

    2.添加内容页

    3.集成第三方路由和tab页

    4.自定义TabBar中间按钮,实现播放时旋转动画

    5.耗时代码处理,Loading加载动画

    6.Toast吐司,消息提示

    7.自定义头部导航

    展开全文
  • Hello All, happy reading...I think everybody knows, what is Flutter and React native? 我想所有人都知道,什么是 Flutter 和 React Native ? If you don’t have an answer, no worries let’s tighten you...

    Hello All, happy reading.
     

    大家好,祝阅读愉快。

    I think everybody knows, what is Flutter and React native ?
     

    我想所有人都知道,什么是 Flutter 和 React Native ?

    If you don’t have an answer, no worries let’s tighten your seat belts will have nice drive on mobile platforms and their comparisons, and what to choose for better development.

    如果你没有答案,请不用担心,让我们系好安全带,开启一段美妙的旅程,通过下文详细介绍两者的区别,以便让我们对两者有更清晰的认识,做出最更好的选择。

    Before dive into the Flutter vs React Native comparisons, let us have a quick look on evolution of Mobile platforms.
     

    在深入了解Flutter vs React Native比较之前,让我们快速了解一下移动平台的发展。

    Mobile Platforms

    The leading mobile platforms are iOS (Apple), Android (Google), where as we had Windows and Blackberry platforms also available, based on the performance, usage and simple UI these two platforms are no where in the market and they are outdated.
     

    目前市场上主流的平台是 iOS (苹果) 和 Android(谷歌),同时还有 Windows 和 Blackbrry 平台。基于性能、使用和简单的用户界面,目前这两个平台已经没落了,可以忽略。

    The following picture demonstrates the clear vision on current ruling platforms
     

    下图展示了当前移动平台统治的一个现状Android & iOS competitors

    Android & iOS competitors

    Mobile Development Approaches

    Mobile app development is complex. To build apps that reach all users, developers must deal with many different operating systems, SDKs, development tools, screen size and form factors, as well as technology landscape that is still in a constant state of flux.
     

    移动应用程序开发很复杂。要构建覆盖所有用户的应用程序,开发人员必须处理许多不同的操作系统,SDK,开发工具,屏幕大小和外形规格,以及仍处于不断变化的技术环境。

    Choosing how to build a mobile app, though, can have the most dramatic effect on the eventual cost, time, and success of a mobile project. This is second only to determining the scope and functionality of the app itself. Failure to match an application’s requirements to the right mobile development approach all but guarantees wasted time and effort–often resulting in a less effective end result.
     

    但是,选择如何构建移动应用程序可以对移动项目的最终成本,时间和成功产生最显着的影响。这仅次于确定应用程序本身的范围和功能。未能将应用程序的要求与正确的移动开发方法相匹配,除了保证浪费时间和精力 - 通常导致最终结果不太有效。

    There are three primary approaches to building mobile apps today: Cross-Platform, Hybrid and Native.

     

    目前构建移动应用程序有三种主要模式:跨平台,混合 和 纯原生。

    1. Native Approach (Platform specific tools)

    Native app development name implies, native apps are built using platform-specific SDKs and development tools provided by the platform vendors. For iOS, that means apps are built using Swift / Objective C in Apple’s XcodeAndroid, that means apps are built using Kotlin / Java in Android Studio. Windows Phone is .NET and Visual Studio, and so on. Every platform has its own SDKs, and often, its own programming language.
     

    原生应用程序开发意味着,原生应用程序是使用平台供应商提供的平台特定的SDK和开发工具构建的。对于iOS,这意味着应用程序是使用苹果的Xcode中的Swift / ObjectiveC构建的。Android,这意味着应用程序是在Android Studio中使用Kotlin / Java构建的。Windows Phone是.NET和Visual Studio,依此类推。每个平台都有自己的SDK,通常也有自己的编程语言。

    The advantage of native mobile apps, of course, is maximum access to the features and APIs available on each platform. If something can be done on a mobile device, then native apps will impose the fewest limits.
     

    当然,原生移动应用程序的优势在于最大限度地访问每个平台上可用的功能和API。如果可以在移动设备上执行某些操作,则原生应用程序将施加最少的限制。。

    “Native architecture tends to offer the richest, most graphically engaging user experience, high performance, and the ability to integrate with native device functions and backend enterprise systems.”

    原生架构倾向于提供最丰富,最具图形吸引力的用户体验,高性能以及与本机设备功能和后端企业系统集成的能力。

    • Hybrid Approach (Mix of cross-platform & native)

    Recognising that most developers, given the choice, would prefer apps that have the “reach” of web and the “richness” of native, hybrid attempts to blend the benefits of web and native mobile app development. According to a recent UI Survey on HTML5, when asked what makes HTML5 development more appealing than other options for writing software, 62% said reach/cross-platform support as one of the biggest benefits. Hybrid apps are developed using standard web technologies, like HTML, JavaScript and CSS(cross-platformand native components, but are able to overcome the limits of “pure” web apps by using platform-specific native “wrappers” to package and deploy the code.
     

    认识到大多数开发者,如果有选择的话,会更喜欢那些“触手可及”的应用程序,以及那些“丰富多彩”的原生混合型应用程序,它们试图将web和原生移动应用程序开发的好处融合在一起。根据最近一项关于HTML5的用户界面调查,当被问及是什么使得HTML5开发比其他编写软件的选项更具吸引力时,62%的人表示,Reach/Cross Platform支持是最大的好处之一。混合应用程序使用标准的Web技术(如HTML、javascript和CSS(跨平台)和原生组件)开发,但能够通过使用特定于平台的本机“包装器”打包和部署代码来克服“纯”Web应用程序的限制。

    The native wrappers allow hybrid apps to be installed on devices, deploy via app stores and access native device APIs via JavaScript. Since hybrid apps are built with web technologies,

     

    原生打包程序允许在设备上安装混合应用程序,通过应用程序存储进行部署,并通过JavaScript访问本机设备API。因为混合应用程序是用网络技术构建的。

    • Cross-Platform Approach (Java Script, HTML, CSS)

      跨平台方法(Java脚本、HTML、CSS)

    “ Write once deploy Twice” | Cross-platform development is the practice of developing software products or services for multiple platforms or software environments. Engineers and developers use various methods to accommodate different operating systems or environments for one application or product.
     

    “一份代码,部署两次”|跨平台开发是为多个平台或软件环境开发软件产品或服务的实践。工程师和开发人员使用各种方法为一个应用程序或产品适应不同的操作系统或环境。

    Different cross platform approaches

    The idea of cross-platform development is that a software application or product should work well in more than one specific digital habitat. This capability is typically pursued in order to sell software for more than one proprietary operating system, such as to accommodate use on both Google and Apple platforms.
     

    跨平台开发的思想是,软件应用程序或产品应该在多个特定的数字环境中有良好的运行。这种能力通常是为了向多个专有操作系统销售软件而实现的,例如适用于Google和Apple平台。

    In general, cross-platform development can make a program less efficient. For example, it can require redundant processes or file storage folders for the various systems that it’s supposed to support. It may also require that a program be “dumbed down” to accommodate less sophisticated software environments. However, in many cases, the makers of software figured out that the limitations of cross-platform development are worth dealing with in order to offer an application or product to a wider set of users.
     

    一般来说,跨平台开发会降低程序的效率。例如,对于它应该支持的各种系统,它可能需要冗余的进程或文件存储文件夹。它还可能需要“简化”程序以适应不太复杂的软件环境。然而,在许多情况下,软件制造商发现,为了向更广泛的用户提供应用程序或产品,跨平台开发的局限性是值得处理的。

    There are many cross-platform approaches are in the market, and “React Native” is the best of all the other approaches, but it has many advantages and dis-advantages.
     

    市场上有许多跨平台的开发框架,而“React Native”是目前所有已知的框架中使用最多的,但它有许多优点和不足之处。

    Here comes the Google’s Flutter with powerful features and nice architecture.

    Flutter vs React Native :

    React native

    React native is an open source mobile application framework created by Facebook. It is a cross platform technology where we can develop both iOS and Android apps.
     

    React Native是一个由Facebook创建的开源移动应用程序框架。它是一种跨平台技术,我们可以在其中开发iOS和Android应用程序。

    React native there for in the market around 4 years (March 26, 2015) and lot companies uses it, like MyntraUber Eats, Instagram and Facebook. It is a quiet matured community, lot of applications are developed and launched in the market.
     

    React Native 已经发展了4年左右(2015年3月26日)市场上很多企业都在使用它,比如Myntra、Uber Eats、Instagram和Facebook。它是一个安静成熟的社区,许多应用程序都在市场上开发和发布。

    Flutter

    “Build beautiful native apps for iOS/Android from a single code base”.
     

    “从单一代码库构建适用于iOS / Android的精美原生应用”。

    Flutter is an open source mobile application framework created by Google. It is used to develop applications for Android and iOS, as well as being the primary method of creating applications for Google Fuchsia.

    Flutter是由Google创建的开源移动应用程序框架。它用于开发Android和iOS的应用程序,以及为Google Fuchsia创建应用程序的主要方法。

    Slowly all featured companies adopting Flutter, like Alibaba Group, Groupon, Hamilton and Google.

    慢慢地,Flutter逐渐被一些企业所尝试,如阿里巴巴集团、Groupon、汉密尔顿和谷歌。

    1. Architecture

    React Native architecture

    Flutter architecture

    2. Feature Set

    Feature set comparisons

    3. Corporate Sponsors

    Corporate sponsors

    4. Language Choice

    Language choice comparisons

    5. Difficulty Curve

    Difficulty level comparisons

    6. Productivity

    Productivity comparisons

    7. State Management

    State management comparisons

    Flutter take on featured companies

    Conclusion

    Flutter has more advantages over React Native, like Machine Code (AOT), Bridge less architecture, Widgets support (Flutter components) and most important it is Google’s product. They have already enormous experience on mobile platform (Android).

    My take on these different platforms is, every technology has their own strengths and weaknesses.

    “Based on Use Case”, we need to choose mobile platforms.

    If you are dealing with lot of platform specific API’s like HealthKit, Sensor based features, advanced animations and so on, Native Platform is better choice.

    If you are new to mobile technology and want develop both Android and iOS apps better to start with Flutter.

    If your application is dealing with less animation and lot of UI stuff, and you are good at Java Script and web frameworks React Native is better choice.

    If your application dealing with all the above cases and based on your convenience choose Hybrid app development.

    展开全文
  • 运行npm run eject 将webpack的配置暴露出来如下: PS C:\Users\29525\Desktop\me\my-app> npm run eject > my-app@0.1.0 eject C:\Users\29525\Desktop\me\my-app ...NOTE: Create React App 2...
    1. 运行npm run eject 将webpack的配置暴露出来如下:
    
    PS C:\Users\29525\Desktop\me\my-app> npm run eject
    
    > my-app@0.1.0 eject C:\Users\29525\Desktop\me\my-app
    > react-scripts eject
    
    NOTE: Create React App 2 supports TypeScript, Sass, CSS Modules and more without ejecting: https://reactjs.org/blog/2018/10/01/create-react-app-v2.html
    
    ? Are you sure you want to eject? This action is permanent. Yes
    Ejecting...
    
    Copying files into C:\Users\29525\Desktop\me\my-app
      Adding \config\env.js to the project
      Adding \config\paths.js to the project
      Adding \config\webpack.config.js to the project
      Adding \config\webpackDevServer.config.js to the project
      Adding \config\jest\cssTransform.js to the project
      Adding \config\jest\fileTransform.js to the project
      Adding \scripts\build.js to the project
      Adding \scripts\start.js to the project
      Adding \scripts\test.js to the project
    
    Updating the dependencies
      Removing react-scripts from dependencies
      Adding @babel/core to dependencies
      Adding @svgr/webpack to dependencies
      Adding babel-core to dependencies
      Adding babel-eslint to dependencies
      Adding babel-jest to dependencies
      Adding babel-loader to dependencies
      Adding babel-plugin-named-asset-import to dependencies
      Adding babel-preset-react-app to dependencies
      Adding bfj to dependencies
      Adding case-sensitive-paths-webpack-plugin to dependencies
      Adding css-loader to dependencies
      Adding dotenv to dependencies
      Adding dotenv-expand to dependencies
      Adding eslint to dependencies
      Adding eslint-config-react-app to dependencies
      Adding eslint-loader to dependencies
      Adding eslint-plugin-flowtype to dependencies
      Adding eslint-plugin-import to dependencies
      Adding eslint-plugin-jsx-a11y to dependencies
      Adding eslint-plugin-react to dependencies
      Adding file-loader to dependencies
      Adding fs-extra to dependencies
      Adding html-webpack-plugin to dependencies
      Adding identity-obj-proxy to dependencies
      Adding jest to dependencies
      Adding jest-pnp-resolver to dependencies
      Adding jest-resolve to dependencies
      Adding jest-watch-typeahead to dependencies
      Adding mini-css-extract-plugin to dependencies
      Adding optimize-css-assets-webpack-plugin to dependencies
      Adding pnp-webpack-plugin to dependencies
      Adding postcss-flexbugs-fixes to dependencies
      Adding postcss-loader to dependencies
      Adding postcss-preset-env to dependencies
      Adding postcss-safe-parser to dependencies
      Adding react-app-polyfill to dependencies
      Adding react-dev-utils to dependencies
      Adding resolve to dependencies
      Adding sass-loader to dependencies
      Adding style-loader to dependencies
      Adding terser-webpack-plugin to dependencies
      Adding url-loader to dependencies
      Adding webpack to dependencies
      Adding webpack-dev-server to dependencies
      Adding webpack-manifest-plugin to dependencies
      Adding workbox-webpack-plugin to dependencies
    
    Updating the scripts
      Replacing "react-scripts start" with "node scripts/start.js"
      Replacing "react-scripts build" with "node scripts/build.js"
      Replacing "react-scripts test" with "node scripts/test.js"
    
    Configuring package.json
      Adding Jest configuration
      Adding Babel preset
      Adding ESLint configuration
    
    
    1. 修改webpack.config.js的webpack配置文件:由于脚手架默认集成了sass的配置。所以直接模改less就ok了。如下图
      在这里插入图片描述
    2. *使用如下图

    在这里插入图片描述

    1. 附上完整的webpack.config.js
    'use strict';
    
    const fs = require('fs');
    const path = require('path');
    const webpack = require('webpack');
    const resolve = require('resolve');
    const PnpWebpackPlugin = require('pnp-webpack-plugin');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
    const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
    const TerserPlugin = require('terser-webpack-plugin');
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    const safePostCssParser = require('postcss-safe-parser');
    const ManifestPlugin = require('webpack-manifest-plugin');
    const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
    const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
    const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
    const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
    const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
    const paths = require('./paths');
    const getClientEnvironment = require('./env');
    const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
    const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
    const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
    
    
    // Source maps are resource heavy and can cause out of memory issue for large source files.
    const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
    // Some apps do not need the benefits of saving a web request, so not inlining the chunk
    // makes for a smoother build process.
    const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
    
    // Check if TypeScript is setup
    const useTypeScript = fs.existsSync(paths.appTsConfig);
    
    // style files regexes
    const cssRegex = /\.css$/;
    const cssModuleRegex = /\.module\.css$/;
    const sassRegex = /\.(scss|sass)$/;
    const sassModuleRegex = /\.module\.(scss|sass)$/;
    const lessRegex = /\.less$/;
    const lessModuleRegex = /\.module\.less$/;
    
    // This is the production and development configuration.
    // It is focused on developer experience, fast rebuilds, and a minimal bundle.
    module.exports = function(webpackEnv) {
      const isEnvDevelopment = webpackEnv === 'development';
      const isEnvProduction = webpackEnv === 'production';
    
      // Webpack uses `publicPath` to determine where the app is being served from.
      // It requires a trailing slash, or the file assets will get an incorrect path.
      // In development, we always serve from the root. This makes config easier.
      const publicPath = isEnvProduction
        ? paths.servedPath
        : isEnvDevelopment && '/';
      // Some apps do not use client-side routing with pushState.
      // For these, "homepage" can be set to "." to enable relative asset paths.
      const shouldUseRelativeAssetPaths = publicPath === './';
    
      // `publicUrl` is just like `publicPath`, but we will provide it to our app
      // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
      // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
      const publicUrl = isEnvProduction
        ? publicPath.slice(0, -1)
        : isEnvDevelopment && '';
      // Get environment variables to inject into our app.
      const env = getClientEnvironment(publicUrl);
    
      // common function to get style loaders
      const getStyleLoaders = (cssOptions, preProcessor) => {
        const loaders = [
          isEnvDevelopment && require.resolve('style-loader'),
          isEnvProduction && {
            loader: MiniCssExtractPlugin.loader,
            options: Object.assign(
              {},
              shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
            ),
          },
          {
            loader: require.resolve('css-loader'),
            options: cssOptions,
          },
          {
            // Options for PostCSS as we reference these options twice
            // Adds vendor prefixing based on your specified browser support in
            // package.json
            loader: require.resolve('postcss-loader'),
            options: {
              // Necessary for external CSS imports to work
              // https://github.com/facebook/create-react-app/issues/2677
              ident: 'postcss',
              plugins: () => [
                require('postcss-flexbugs-fixes'),
                require('postcss-preset-env')({
                  autoprefixer: {
                    flexbox: 'no-2009',
                  },
                  stage: 3,
                }),
              ],
              sourceMap: isEnvProduction && shouldUseSourceMap,
            },
          },
        ].filter(Boolean);
        if (preProcessor) {
          loaders.push({
            loader: require.resolve(preProcessor),
            options: {
              sourceMap: isEnvProduction && shouldUseSourceMap,
            },
          });
        }
        return loaders;
      };
    
      return {
        mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
        // Stop compilation early in production
        bail: isEnvProduction,
        devtool: isEnvProduction
          ? shouldUseSourceMap
            ? 'source-map'
            : false
          : isEnvDevelopment && 'cheap-module-source-map',
        // These are the "entry points" to our application.
        // This means they will be the "root" imports that are included in JS bundle.
        entry: [
          // Include an alternative client for WebpackDevServer. A client's job is to
          // connect to WebpackDevServer by a socket and get notified about changes.
          // When you save a file, the client will either apply hot updates (in case
          // of CSS changes), or refresh the page (in case of JS changes). When you
          // make a syntax error, this client will display a syntax error overlay.
          // Note: instead of the default WebpackDevServer client, we use a custom one
          // to bring better experience for Create React App users. You can replace
          // the line below with these two lines if you prefer the stock client:
          // require.resolve('webpack-dev-server/client') + '?/',
          // require.resolve('webpack/hot/dev-server'),
          isEnvDevelopment &&
            require.resolve('react-dev-utils/webpackHotDevClient'),
          // Finally, this is your app's code:
          paths.appIndexJs,
          // We include the app code last so that if there is a runtime error during
          // initialization, it doesn't blow up the WebpackDevServer client, and
          // changing JS code would still trigger a refresh.
        ].filter(Boolean),
        output: {
          // The build folder.
          path: isEnvProduction ? paths.appBuild : undefined,
          // Add /* filename */ comments to generated require()s in the output.
          pathinfo: isEnvDevelopment,
          // There will be one main bundle, and one file per asynchronous chunk.
          // In development, it does not produce real files.
          filename: isEnvProduction
            ? 'static/js/[name].[contenthash:8].js'
            : isEnvDevelopment && 'static/js/bundle.js',
          // There are also additional JS chunk files if you use code splitting.
          chunkFilename: isEnvProduction
            ? 'static/js/[name].[contenthash:8].chunk.js'
            : isEnvDevelopment && 'static/js/[name].chunk.js',
          // We inferred the "public path" (such as / or /my-project) from homepage.
          // We use "/" in development.
          publicPath: publicPath,
          // Point sourcemap entries to original disk location (format as URL on Windows)
          devtoolModuleFilenameTemplate: isEnvProduction
            ? info =>
                path
                  .relative(paths.appSrc, info.absoluteResourcePath)
                  .replace(/\\/g, '/')
            : isEnvDevelopment &&
              (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
        },
        optimization: {
          minimize: isEnvProduction,
          minimizer: [
            // This is only used in production mode
            new TerserPlugin({
              terserOptions: {
                parse: {
                  // we want terser to parse ecma 8 code. However, we don't want it
                  // to apply any minfication steps that turns valid ecma 5 code
                  // into invalid ecma 5 code. This is why the 'compress' and 'output'
                  // sections only apply transformations that are ecma 5 safe
                  // https://github.com/facebook/create-react-app/pull/4234
                  ecma: 8,
                },
                compress: {
                  ecma: 5,
                  warnings: false,
                  // Disabled because of an issue with Uglify breaking seemingly valid code:
                  // https://github.com/facebook/create-react-app/issues/2376
                  // Pending further investigation:
                  // https://github.com/mishoo/UglifyJS2/issues/2011
                  comparisons: false,
                  // Disabled because of an issue with Terser breaking valid code:
                  // https://github.com/facebook/create-react-app/issues/5250
                  // Pending futher investigation:
                  // https://github.com/terser-js/terser/issues/120
                  inline: 2,
                },
                mangle: {
                  safari10: true,
                },
                output: {
                  ecma: 5,
                  comments: false,
                  // Turned on because emoji and regex is not minified properly using default
                  // https://github.com/facebook/create-react-app/issues/2488
                  ascii_only: true,
                },
              },
              // Use multi-process parallel running to improve the build speed
              // Default number of concurrent runs: os.cpus().length - 1
              parallel: true,
              // Enable file caching
              cache: true,
              sourceMap: shouldUseSourceMap,
            }),
            // This is only used in production mode
            new OptimizeCSSAssetsPlugin({
              cssProcessorOptions: {
                parser: safePostCssParser,
                map: shouldUseSourceMap
                  ? {
                      // `inline: false` forces the sourcemap to be output into a
                      // separate file
                      inline: false,
                      // `annotation: true` appends the sourceMappingURL to the end of
                      // the css file, helping the browser find the sourcemap
                      annotation: true,
                    }
                  : false,
              },
            }),
          ],
          // Automatically split vendor and commons
          // https://twitter.com/wSokra/status/969633336732905474
          // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
          splitChunks: {
            chunks: 'all',
            name: false,
          },
          // Keep the runtime chunk separated to enable long term caching
          // https://twitter.com/wSokra/status/969679223278505985
          runtimeChunk: true,
        },
        resolve: {
          // This allows you to set a fallback for where Webpack should look for modules.
          // We placed these paths second because we want `node_modules` to "win"
          // if there are any conflicts. This matches Node resolution mechanism.
          // https://github.com/facebook/create-react-app/issues/253
          modules: ['node_modules'].concat(
            // It is guaranteed to exist because we tweak it in `env.js`
            process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
          ),
          // These are the reasonable defaults supported by the Node ecosystem.
          // We also include JSX as a common component filename extension to support
          // some tools, although we do not recommend using it, see:
          // https://github.com/facebook/create-react-app/issues/290
          // `web` extension prefixes have been added for better support
          // for React Native Web.
          extensions: paths.moduleFileExtensions
            .map(ext => `.${ext}`)
            .filter(ext => useTypeScript || !ext.includes('ts')),
          alias: {
            // Support React Native Web
            // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
            'react-native': 'react-native-web',
          },
          plugins: [
            // Adds support for installing with Plug'n'Play, leading to faster installs and adding
            // guards against forgotten dependencies and such.
            PnpWebpackPlugin,
            // Prevents users from importing files from outside of src/ (or node_modules/).
            // This often causes confusion because we only process files within src/ with babel.
            // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
            // please link the files into your node_modules/ and let module-resolution kick in.
            // Make sure your source files are compiled, as they will not be processed in any way.
            new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
          ],
        },
        resolveLoader: {
          plugins: [
            // Also related to Plug'n'Play, but this time it tells Webpack to load its loaders
            // from the current package.
            PnpWebpackPlugin.moduleLoader(module),
          ],
        },
        module: {
          strictExportPresence: true,
          rules: [
            // Disable require.ensure as it's not a standard language feature.
            { parser: { requireEnsure: false } },
    
            // First, run the linter.
            // It's important to do this before Babel processes the JS.
            {
              test: /\.(js|mjs|jsx)$/,
              enforce: 'pre',
              use: [
                {
                  options: {
                    formatter: require.resolve('react-dev-utils/eslintFormatter'),
                    eslintPath: require.resolve('eslint'),
                    
                  },
                  loader: require.resolve('eslint-loader'),
                },
              ],
              include: paths.appSrc,
            },
            {
              // "oneOf" will traverse all following loaders until one will
              // match the requirements. When no loader matches it will fall
              // back to the "file" loader at the end of the loader list.
              oneOf: [
                // "url" loader works like "file" loader except that it embeds assets
                // smaller than specified limit in bytes as data URLs to avoid requests.
                // A missing `test` is equivalent to a match.
                {
                  test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
                  loader: require.resolve('url-loader'),
                  options: {
                    limit: 10000,
                    name: 'static/media/[name].[hash:8].[ext]',
                  },
                },
                // Process application JS with Babel.
                // The preset includes JSX, Flow, TypeScript, and some ESnext features.
                {
                  test: /\.(js|mjs|jsx|ts|tsx)$/,
                  include: paths.appSrc,
                  loader: require.resolve('babel-loader'),
                  options: {
                    customize: require.resolve(
                      'babel-preset-react-app/webpack-overrides'
                    ),
                    
                    plugins: [
                      [
                        require.resolve('babel-plugin-named-asset-import'),
                        {
                          loaderMap: {
                            svg: {
                              ReactComponent: '@svgr/webpack?-svgo,+ref![path]',
                            },
                          },
                        },
                      ],
                    ],
                    // This is a feature of `babel-loader` for webpack (not Babel itself).
                    // It enables caching results in ./node_modules/.cache/babel-loader/
                    // directory for faster rebuilds.
                    cacheDirectory: true,
                    cacheCompression: isEnvProduction,
                    compact: isEnvProduction,
                  },
                },
                // Process any JS outside of the app with Babel.
                // Unlike the application JS, we only compile the standard ES features.
                {
                  test: /\.(js|mjs)$/,
                  exclude: /@babel(?:\/|\\{1,2})runtime/,
                  loader: require.resolve('babel-loader'),
                  options: {
                    babelrc: false,
                    configFile: false,
                    compact: false,
                    presets: [
                      [
                        require.resolve('babel-preset-react-app/dependencies'),
                        { helpers: true },
                      ],
                    ],
                    cacheDirectory: true,
                    cacheCompression: isEnvProduction,
                    
                    // If an error happens in a package, it's possible to be
                    // because it was compiled. Thus, we don't want the browser
                    // debugger to show the original code. Instead, the code
                    // being evaluated would be much more helpful.
                    sourceMaps: false,
                  },
                },
                // "postcss" loader applies autoprefixer to our CSS.
                // "css" loader resolves paths in CSS and adds assets as dependencies.
                // "style" loader turns CSS into JS modules that inject <style> tags.
                // In production, we use MiniCSSExtractPlugin to extract that CSS
                // to a file, but in development "style" loader enables hot editing
                // of CSS.
                // By default we support CSS Modules with the extension .module.css
                {
                  test: cssRegex,
                  exclude: cssModuleRegex,
                  use: getStyleLoaders({
                    importLoaders: 1,
                    sourceMap: isEnvProduction && shouldUseSourceMap,
                  }),
                  // Don't consider CSS imports dead code even if the
                  // containing package claims to have no side effects.
                  // Remove this when webpack adds a warning or an error for this.
                  // See https://github.com/webpack/webpack/issues/6571
                  sideEffects: true,
                },
                // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
                // using the extension .module.css
                {
                  test: cssModuleRegex,
                  use: getStyleLoaders({
                    importLoaders: 1,
                    sourceMap: isEnvProduction && shouldUseSourceMap,
                    modules: true,
                    getLocalIdent: getCSSModuleLocalIdent,
                  }),
                },
                // Opt-in support for SASS (using .scss or .sass extensions).
                // By default we support SASS Modules with the
                // extensions .module.scss or .module.sass
                {
                  test: sassRegex,
                  exclude: sassModuleRegex,
                  use: getStyleLoaders(
                    {
                      importLoaders: 2,
                      sourceMap: isEnvProduction && shouldUseSourceMap,
                    },
                    'sass-loader'
                  ),
                  // Don't consider CSS imports dead code even if the
                  // containing package claims to have no side effects.
                  // Remove this when webpack adds a warning or an error for this.
                  // See https://github.com/webpack/webpack/issues/6571
                  sideEffects: true,
                },
                // Adds support for CSS Modules, but using SASS
                // using the extension .module.scss or .module.sass
                {
                  test: sassModuleRegex,
                  use: getStyleLoaders(
                    {
                      importLoaders: 2,
                      sourceMap: isEnvProduction && shouldUseSourceMap,
                      modules: true,
                      getLocalIdent: getCSSModuleLocalIdent,
                    },
                    'sass-loader'
                  ),
                },
                // 配置less
                {
                  test: lessRegex,
                  exclude: sassModuleRegex,
                  use: getStyleLoaders(
                    {
                      importLoaders: 2,
                      sourceMap: isEnvProduction && shouldUseSourceMap,
                    },
                    'less-loader'
                  ),
                  // Don't consider CSS imports dead code even if the
                  // containing package claims to have no side effects.
                  // Remove this when webpack adds a warning or an error for this.
                  // See https://github.com/webpack/webpack/issues/6571
                  sideEffects: true,
                },
                // Adds support for CSS Modules, but using SASS
                // using the extension .module.scss or .module.sass
                {
                  test: lessModuleRegex,
                  use: getStyleLoaders(
                    {
                      importLoaders: 2,
                      sourceMap: isEnvProduction && shouldUseSourceMap,
                      modules: true,
                      getLocalIdent: getCSSModuleLocalIdent,
                    },
                    'less-loader'
                  ),
                },
                // 配置less
                // "file" loader makes sure those assets get served by WebpackDevServer.
                // When you `import` an asset, you get its (virtual) filename.
                // In production, they would get copied to the `build` folder.
                // This loader doesn't use a "test" so it will catch all modules
                // that fall through the other loaders.
                {
                  loader: require.resolve('file-loader'),
                  // Exclude `js` files to keep "css" loader working as it injects
                  // its runtime that would otherwise be processed through "file" loader.
                  // Also exclude `html` and `json` extensions so they get processed
                  // by webpacks internal loaders.
                  exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
                  options: {
                    name: 'static/media/[name].[hash:8].[ext]',
                  },
                },
                // ** STOP ** Are you adding a new loader?
                // Make sure to add the new loader(s) before the "file" loader.
              ],
            },
          ],
        },
        plugins: [
          // Generates an `index.html` file with the <script> injected.
          new HtmlWebpackPlugin(
            Object.assign(
              {},
              {
                inject: true,
                template: paths.appHtml,
              },
              isEnvProduction
                ? {
                    minify: {
                      removeComments: true,
                      collapseWhitespace: true,
                      removeRedundantAttributes: true,
                      useShortDoctype: true,
                      removeEmptyAttributes: true,
                      removeStyleLinkTypeAttributes: true,
                      keepClosingSlash: true,
                      minifyJS: true,
                      minifyCSS: true,
                      minifyURLs: true,
                    },
                  }
                : undefined
            )
          ),
          // Inlines the webpack runtime script. This script is too small to warrant
          // a network request.
          isEnvProduction &&
            shouldInlineRuntimeChunk &&
            new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime~.+[.]js/]),
          // Makes some environment variables available in index.html.
          // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
          // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
          // In production, it will be an empty string unless you specify "homepage"
          // in `package.json`, in which case it will be the pathname of that URL.
          // In development, this will be an empty string.
          new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
          // This gives some necessary context to module not found errors, such as
          // the requesting resource.
          new ModuleNotFoundPlugin(paths.appPath),
          // Makes some environment variables available to the JS code, for example:
          // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
          // It is absolutely essential that NODE_ENV is set to production
          // during a production build.
          // Otherwise React will be compiled in the very slow development mode.
          new webpack.DefinePlugin(env.stringified),
          // This is necessary to emit hot updates (currently CSS only):
          isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
          // Watcher doesn't work well if you mistype casing in a path so we use
          // a plugin that prints an error when you attempt to do this.
          // See https://github.com/facebook/create-react-app/issues/240
          isEnvDevelopment && new CaseSensitivePathsPlugin(),
          // If you require a missing module and then `npm install` it, you still have
          // to restart the development server for Webpack to discover it. This plugin
          // makes the discovery automatic so you don't have to restart.
          // See https://github.com/facebook/create-react-app/issues/186
          isEnvDevelopment &&
            new WatchMissingNodeModulesPlugin(paths.appNodeModules),
          isEnvProduction &&
            new MiniCssExtractPlugin({
              // Options similar to the same options in webpackOptions.output
              // both options are optional
              filename: 'static/css/[name].[contenthash:8].css',
              chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
            }),
          // Generate a manifest file which contains a mapping of all asset filenames
          // to their corresponding output file so that tools can pick it up without
          // having to parse `index.html`.
          new ManifestPlugin({
            fileName: 'asset-manifest.json',
            publicPath: publicPath,
          }),
          // Moment.js is an extremely popular library that bundles large locale files
          // by default due to how Webpack interprets its code. This is a practical
          // solution that requires the user to opt into importing specific locales.
          // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
          // You can remove this if you don't use Moment.js:
          new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
          // Generate a service worker script that will precache, and keep up to date,
          // the HTML & assets that are part of the Webpack build.
          isEnvProduction &&
            new WorkboxWebpackPlugin.GenerateSW({
              clientsClaim: true,
              exclude: [/\.map$/, /asset-manifest\.json$/],
              importWorkboxFrom: 'cdn',
              navigateFallback: publicUrl + '/index.html',
              navigateFallbackBlacklist: [
                // Exclude URLs starting with /_, as they're likely an API call
                new RegExp('^/_'),
                // Exclude URLs containing a dot, as they're likely a resource in
                // public/ and not a SPA route
                new RegExp('/[^/]+\\.[^/]+$'),
              ],
            }),
          // TypeScript type checking
          useTypeScript &&
            new ForkTsCheckerWebpackPlugin({
              typescript: resolve.sync('typescript', {
                basedir: paths.appNodeModules,
              }),
              async: isEnvDevelopment,
              useTypescriptIncrementalApi: true,
              checkSyntacticErrors: true,
              tsconfig: paths.appTsConfig,
              reportFiles: [
                '**',
                '!**/*.json',
                '!**/__tests__/**',
                '!**/?(*.)(spec|test).*',
                '!**/src/setupProxy.*',
                '!**/src/setupTests.*',
              ],
              watch: paths.appSrc,
              silent: true,
              // The formatter is invoked directly in WebpackDevServerUtils during development
              formatter: isEnvProduction ? typescriptFormatter : undefined,
            }),
        ].filter(Boolean),
        // Some libraries import Node modules but don't use them in the browser.
        // Tell Webpack to provide empty mocks for them so importing them works.
        node: {
          module: 'empty',
          dgram: 'empty',
          dns: 'mock',
          fs: 'empty',
          net: 'empty',
          tls: 'empty',
          child_process: 'empty',
        },
        // Turn off performance processing because we utilize
        // our own hints via the FileSizeReporter
        performance: false,
      };
    };
    
    
    展开全文
  • react-native-lesson

    2019-08-21 14:08:55
    一、React-Native入门指南 lesson1: Hello React-Native lesson2: 认识代码结构 lesson3: css和布局 lesson4: 学会React-Native布局(一) lesson5: 学会React-Native布局(二) lesson6: UI组件 lesson7: ...

    一、React-Native入门指南

    二、上线参考案例

    厕所在哪(已上线,可以在iOS App store下载):https://github.com/vczero/toilet

    React Native完全开发的独立App,支持ES6语法,已更新到0.30版本,App store上线第一周破百,工具类App。      
    

    三、一般参考案例:

    四、实例组件

    AsyncStorge调试工具: https://github.com/vczero/rn-cook
    React Native日历组件: https://github.com/vczero/react-native-calendar
    React Native Tab二级菜单组件:https://github.com/vczero/react-native-tab

    五、基于React Native开发原生iOS应用

    六、《React Native入门与实战》图书购买地址:

         目前京东、互动、天猫均有售                    
         http://item.jd.com/11844102.html     (京东自营)                    
         http://item.jd.com/10089706582.html        
         http://item.jd.com/10089810271.html       
    

    七、《React Native入门与实战》章节分配

    章节标题作者
    第1章 React Native简介 王利华
    第2章 React Native开发基础 魏晓军
    第3章 常用组件及其实践 王利华
    第4章 常用API及其实践 王利华
    第5章 Native扩展 冯诚祺
    第6章 组件封装 王利华
    第7章 热更新和上架 冯诚祺
    第8章 企业内部通讯录应用开发 王利华
    第9章 基于LBS的应用开发 王利华
    第10章 豆搜App 王利华

    转载于:https://www.cnblogs.com/chenzxl/p/7885002.html

    展开全文
  • 总第357篇2019年 第35篇2018年,我们开源了React Native组件库——React Native组件库继续优化,实现beeshell 2.0升级,开源38...

    总第357篇

    2019年 第35篇

    2018年,我们开源了React Native组件库——beeshell 1.0。时隔一年,我们对React Native组件库继续优化,实现beeshell 2.0升级,开源38个功能。希望更好的服务社区,同时也希望利用社区力量丰富React Native组件库

    引言

    随着 React Native(以下简称 RN)技术的出现,大前端的发展趋势已经势不可挡,跨平台技术因其通用性、低成本、高效率的特点,逐渐成为行业追捧的热点。

    为了进一步降低开发成本、提升产品迭代的效率,美团开始推广使用 RN 技术。随之而来,相关业务方开始提出对 RN 组件库的诉求。2018 年 11 月,公司内部发起了 RN 组件库建设,旨在提供全公司共用的组件库。鉴于我们团队在开源 beeshell 1.0时,积累了丰富的经验,于是就加入到了公司级 RN 组件库的项目共建中。完成组件库开发后,我们决定将共建的成果贡献出来,并以 beeshell 升级 2.0 的形式进行开源,共计开源 38(33 个组件与 5 个工具)个功能。在服务社区的同时,也希望借助社区的力量,进一步完善组件库。

    beeshell 2.0 效果图如下:

    图D

    背景

    前端开发(包括 Web 与 Native 开发)是图形用户界面(GUI)开发的一种。纵观各种 Web 以及 Native 产品界面,发现它们都是由一些基本组件(控件、元素)组合而成。受益于组件化、模块化的开发方式,前端 MV* 框架(如:AngularJS、React、Vue.js 等)也得以蓬勃发展。借助这些组件化框架,前端开发构建用户界面的过程,本质上是:开发组件,处理组件间的组合与通信。

    这样看来,如果实现一套通用的组件并有效复用,便可以大幅减少开发组件的工作,进而提升前端开发的效率。各个业务方对 RN 组件库的诉求,目的也在于此:降低成本,提升效率。

    然而,公司内不同事业部的业务场景和产品功能不尽相同,如何通过一套组件,来有效的支撑外卖、配送、酒旅及其他事业部的业务需求?这无疑对组件库提出了更高的要求:

    • UI 风格一致性。在同一个业务中,各个组件要有一致的 UI 风格,保证用户体验、塑造品牌形象。

    • 通用性。可以支持不同的业务方,可以灵活定制不同的业务需求,最大化组件复用率,减少重复开发。

    • 易用性。组件的功能、行为表现符合开发者的直观感受,易于学习和使用、减轻记忆负担;功能丰富,可以支持多种业务场景,支持特定业务场景的多种情况。

    • 稳定性。RN 组件库需要同时支持 iOS、Android、Web 三个平台,组件要在三个平台可用、可靠、稳定。

    简而言之,组件库的目标是通用、易用、稳定、灵活。

    系统设计

    图H

    我们的目标是提供一套通用、易用、稳定、灵活的组件。然而,对一个组件来说,如果通用性、灵活性强,则易用性、稳定性势必较差。如何合理的处理这个矛盾呢?

    为解决这个矛盾,我们使用了“关注度分离”的设计原则:首先将组件库需要支持的功能与特性进行分解;进而仔细研究特性的不同侧面(关注点),并提供相应的解决方案;最后整合各独立方案,解决冲突部分,形成整体的解决方案。

    “关注度分离”的设计原则,贯穿整个组件库的设计与实现,是组件库的核心思想。以该原则为基础,衍生出了项目拆分、组件分层、功能解耦等具体方案,实现了一个组件库体系,保证了可以兼顾到相互矛盾的两个方面,实现最终目标。

    架构升级

    图F

    beeshell 2.0 与 1.0 的架构在整体上保持一致,共分成四层:业务层、组件库(体系)、RN 层、系统层。而 beeshell 2.0 的架构升级,则主要体现在第二层与第三层。

    第二层:组件库体系

    组件库由 1.0 的一个项目演变成 2.0 的三个项目(版本),形成组件库体系。具体包含:公司通用版本MTD、外卖定制版本Roo和开源版本beeshell

    这种项目拆分的方式,符合“关注度分离”的设计原则,三个版本有各自不同的关注点:

    • MTD 的关注点是通用性、灵活性,所以提供的是基础、通用的组件。组件的扩展能力极强,可以满足多个业务方的定制化需求。

    • Roo 是对 MTD 的继承与扩展,定制了外卖业务的 UI 风格与功能,通用性减弱,功能性和业务性增强,关注易用性、业务性、一致性。

    • beeshell(准确说是 2.0 版本)是对 Roo 的继承与扩展,与 Roo 相比,去除了过于业务化的组件与功能,纳入并整合社区的需求,关注通用性、易用性、稳定性。

    组件库体系的三个版本,内部的架构设计一致,本文只详细介绍 beeshell 开源版本。

    组件库使用了分层的架构风格,分成了接口层、组件层、工具层以及三端适配:

    • 接口层。汇总所有组件,统一输出,使用全部引入的方式,方便组件使用。另外,为了避免引入过多无用组件,引起资源包过大,也支持组件的按需引入。

    • 组件层。细分为原子、分子、扩展组件。
      与 beeshell 1.0 相比,我们对组件在更细的粒度上进行拆分。同时,层次划分也更加精细、明确。如上图 F 所示:基础组件细分为分子、原子组件。这样,组件的继承、组合方式更加灵活,能够最大化代码复用。而且,原子、分子、扩展组件,各层次组件各司其职,“关注度分离”,兼顾通用性和易用性。

    • 工具层。与 beeshell 1.0 相比,工具更加完备。不但新增了树形结构处理器、校验器等;而且工具的独立性更强,与组件完全解耦,与组件配合实现功能。
      这样,一方面,使得组件实现更加简洁,提升了组件的可维护性;另一方面,可以将工具提供给用户,方便用户开发,提升组件库的功能性、易用性。而且,工具与组件的解耦遵循“关注度分离”的原则。

    • 三端适配。RN 在整体上实现了跨平台,iOS、Android、Web 三端使用一套代码,但是在一些细节方面,例如:特殊 API 的支持、相对位置计算等,各个平台有较大差异。为了更好的支持三端,保证跨平台稳定性,还需要在这一层做很多适配工作。

    第三层:RN 层

    这一层新增了 MRN,MRN 是对 RN 的二次封装,与 RN 底层实现保持一致。组件库在两个平台的表现一致。

    MRN 是基于开源的 RN 框架改造并完善而成的一套动态化方案。MRN 从三个方面弥补 RN 的不足:

    • 动态化能力。RN 本身不支持热更新,MRN 借助公司内发布平台,实现包的上传发布、下载更新、下线回滚等操作。

    • 双端(未来三端)复用问题。从设计原则上保证开发者对平台的差异无明显感知。

    • 保障措施。在开发保障方面:提供脚手架工具、模版工程、开发文档等,方便开发者日常的 MRN 开发工作。提供多级分包机制,业务内部和业务之间能够灵活组织代码;在运行保障方面,提供运行环境隔离,使得不同业务之间页面运行无干扰。提供数据采集和指标大盘,及时发现运行中的问题,同时提供降级能力以应对紧急情况。

    除此之外,整个组件库体系,还具备以下特点:更加完善的测试方案;丰富的代码示例;使用 TypeScript(以下简称 TS)语言进行开发,充分利用 TS 的类型定义与检查;针对每个组件都有详细的文档说明。

    协作模式

    这里通过结构和流程两个方面,详细介绍 beeshell 1.0 与 beeshell 2.0 以及 MTD、Roo 的关系。三个版本之间通过 Git Fork 建立依赖关系,使用源码依赖的方式实现项目拆分。对于用户而言,不同版本的相同组件,底层依赖与实现都是一致的。

    图X

    结构方面:MTD 包含 beeshell 1.0 的全部内容,并进行了组件数量的扩充、组件功能的增强;Roo 包含 MTD,进行了定制与业务扩展;beeshell 2.0 与 Roo 基本一致,去除业务部分,纳入社区需求。

    流程方面:

    • 首先,我们在 beeshell 1.0 的开发以及开源中,积累了丰富的经验。在建设 MTD 公司通用版组件库时,贡献了 50% 的组件;同时,贡献了许多设计模式与思路,大大加速了组件库的建设。

    • 其次,在 MTD 建设完成后,为了更加方便、快速的接入外卖的相关业务,以 MTD 为基础,定制了外卖主题的组件库 Roo,提升了组件库的业务功能性和易用性。外卖相关的业务项目,在接入 Roo 后直接使用,无需再进行主题的定制与调整,在一定程度上节省开发成本。

    • 第三,我们将共建的成果贡献出来,以 Roo 为基础,升级 beeshell 2.0 并开源。将部分过于业务化的组件移除,纳入了社区的相关需求,保证组件库的通用性、易用性与稳定性。

    • 最后,对于公司内部,各个业务方可以以 MTD 为基础进行扩展,定制自己的业务主题组件库(Roo 就是第一个业务扩展);对于社区,各个开发者可以根据实际的业务需求,以 beeshell 为基础,定制扩展组件库。

    综上所述,我们以 beeshell 开发团队为桥梁,建立了美团公司与开源社区之间进行技术交流的通道,美团公司、beeshell 团队以及社区,可以在技术上互帮互助,共同建设、进步。

    方案实现

    UI 风格一致性

    UI 风格一致性的重要之处在于,对内可以保持平台统一性、提升团队效率、打磨细节体验;对外可以塑造品牌形象、减轻用户学习成本、保持产品的体验一致性。UI 风格一致性的关键要素有很多,包括色彩、排版、字体、图标以及交互操作等。从总体上看,可以归纳为两类:样式一致性和动效一致性。

    beeshell 延用了 Roo(袋鼠 UI)的 UI 设计规范,其内容涵盖了 PC 端与移动端、Web 平台与 RN 平台,对 UI 与交互给出了详细的视觉规范,旨在保证外卖事业部,全部产品的 UI 一致性。UI 规范的技术实现方式如下:

    图E

    Roo Theme 向上实现了 UI 规范具体内容,将设计规范统一收敛,向下输出主题变量、组件样式类、通用样式工具等,供各个组件库以及业务方使用。

    Roo Theme 主要使用 Sass 预处理器实现,提供了各个组件的统一样式类。为了满足不同技术栈的需求,主题变量的输出提供了 JavaScript(以下简称 JS)、CSS、Less、Sass、Stylus 多种语言,不同平台、技术栈可以根据情况自由选择。

    beeshell 基于 Roo Theme 输出的 JS 主题变量,通过样式和动效两个方面,保证了 UI 一致性。

    • 样式一致性。通过继承、扩展 Roo Theme,定义全局性的主题变量,用于组件的样式部分定义。主题变量范围涉及品牌色、灰度、字体尺寸、间距以及组件级变量,为组件以及组件之间样式一致性,提供了全面的保障。同时,组件库提供了自定义主题变量的接口,可以重置相关变量的值,对 UI 风格进行一致性调整,实现一键换肤。另外,使用“内部样式 < 主题 < 扩展样式”的样式优先级覆盖策略,保证了样式部分的定制能力(在下文“定制化能力分级设计”章节中详细介绍)。

    • 动效一致性。一方面,依赖主题变量中定义的动画开关变量(主要考虑到一些低端 Android 机器的性能问题),用户可以关闭某个组件的动画;另一方面,依赖组件库的良好分层设计,我们将动画类独立实现,这样可以使用策略模式,将动画方便的集成到任意组件中。

    下文详细介绍样式一致性和动效一致性。

    样式一致性

    样式一致性,可以从色彩和排版两个方面来保证。

    首先,介绍下色彩部分。在 App 应用中,色彩元素扮演的角色仅次于功能。人与计算机的互动,主要是与图形用户界面的交互,而色彩在该交互中起着关键作用。它可以帮助用户查看和理解界面内容,与正确的元素互动,并了解相关操作。每个 App 都会有一套配色方案,并在主要区域使用其基础色彩。

    正因为有无数种色彩组合的可能,在设计一个 App 时,人们的配色方案也有无数种选择。本文不纠结于如何选择一个好的配色方案,而是介绍一个配色方案应该具有哪些元素。一套完整的配色方案,应该包括品牌主色、品牌功能色、中性色。本文以 beeshell 的配色方案举例说明。

    品牌主色

    品牌主色应该是应用中出现最频繁的颜色,通常用来强调 UI 中关键部分的颜色。beeshell 的品牌主色色值为 #ffd800,如下图所示:

    通常,一个产品的 UI 只会有一个品牌主色。然而,像 beeshell 这种品牌主色色值较浅的情况,一个品牌主色并不能够支撑所有的应用场景。此时,可以通过加深主色的方式,再增加几个色值,beeshell 的品牌主色还包括一个加深的色值 #ffa000,用于某些组件的激活状态,如下图所示:

    对于品牌主色的个数,需要根据色值的情况而定,不必过于拘泥规则,只要能有一致性的用户体验即可。

    品牌功能色

    beeshell 的功能色内容与使用场景如下图所示:

    图U

    这四个色值,分别用于一般信息、成功、警告、失败这四种业务场景,以及从这四种业务场景所衍生出的场景。在一定程度上,保证色彩的一致性。

    中性色

    beeshell 的中性色(灰度)的内容与使用场景如下图所示:

    图G

    中性色应用于界面主体文本的颜色,灰度范围的确定,可以大大提升色彩的一致性。

    接下来介绍下排版,具体可以分为字体、间距、边线。

    字体

    beeshell 的字体尺寸(Font Size)集,是基于 12、14、16、20 和 28 的排版比例,如下图所示:

    图B

    这样的排版比例,可以使得界面的文字内容更加协调、流畅,进而提升了排版的一致性。

    对于字重(Font Weight),beeshell 只使用正常(Normal)和加粗(Bold)两种:Normal 用于一般文本;Bold 用于强调,吸引用户注意力。只使用这两种字重,也避免了因为不同字体家族(Font Family),对字重的支持范围不同,而导致视觉差异。

    除了字体尺寸和字重,影响排版的还有字体行高(Line Height)。为了达到适当的可读性和阅读流畅性,可以根据字体的大小和粗细,设定字体行高。默认情况下,RN 应用:行高 = 字体大小 * 1.2。如下图所示:

    图L

    beeshell 使用了默认的字体行高,在一定程度保证了可读性和排版的一致性。

    间距

    间距是 UI 元素与元素之间、父元素与子元素之间的空白区域,一个应用排版风格一致性,很大程度取决于间距。一个组件的最终宽高,应该由内容、内边距以及边框决定,是自适应的,而不应该直接定义宽高。

    对于同一个 App,间距应该在一个合适的范围取值,可以通过定义『小号间距』、『中号间距』、『大号间距』等来划分信息层次。例如 beeshell 的 Button 组件,有三种尺寸。实现效果如下图所示:

    图S

    边线

    边线(边框)部分,需要统一元素的边框宽度、颜色和圆角,边线虽然对 UI 风格的影响较小,但是不可或缺。beeshell 使用的边框宽度为一个物理像素,使用 RN 提供的 StyleSheet.hairlineWidth 接口实现;定义了三种灰度的边框颜色;主要使用 2px 的圆角。

    最后,样式的一致性,还涉及到图标、布局等相关内容,因为 beeshell 对这些内容的涉及较少,这里不做详细介绍。

    动效一致性

    动效展示了应用的组织方式和功能。

    动效可以:

    • 引导用户在视图中的视觉焦点。

    • 提示用户完成手势操作后会发生什么。

    • 暗示元素间的等级和空间关系。

    • 让用户忽视系统背后发生的事情(比如抓取内容、或加载下一个视图)。

    • 使应用更有个性、更优雅、体验更加一致。

    beeshell 组件库基于 Animated 进行了二次封装,提供 FadeAnimated 和 SlideAnimated 两个动画类,支持淡入淡出动画和滑动动画,可以使用策略模式集成到任何组件中。

    beeshell 将逐渐在所有的组件集成这两种动画,保证动效的一致性。下文展示已经实现了动画的组件,先睹为快。

    Button 组件使用 FadeAnimated 类实现动画,动效如下图所示:

    Modal 组件使用 FadeAnimated 类实现动画,动效如下图所示:

    Dropdown 组件使用 SlideAnimated 类实现动画,动效如下图所示:

    定制化能力分级设计

    要开发一套全公司共用的组件,需要同时满足酒旅、外卖 C 端、外卖 B 端以及外卖 M 端等业务的需求,这对组件的定制化能力提出了更高的要求。

    为了进一步增强组件的定制化能力,提升组件的通用性、灵活性;同时,避免属性(API)的无节制增加,进而导致组件难以维护,我们设计了定制化能力分级的策略。

    这里以一个常见的业务场景:底部上滑弹框,来举例说明分级设计。

    图M

    如上图所示:第一个例子比较通用、规范。“区域文字内容”的文案与样式需要支持自定义;第二个例子,需要支持多行文字以及图标,即“区域内容”需要支持自定义;第三个例子,自定义的重点,由区域以及区域内部,转移到区域之间的布局,“区域布局”需要支持自定义。

    区域文字内容、区域内容、区域布局,三个层面的灵活性,可以涵盖一个业务场景所有定制化需求了。以当前业务场景为例,来讲解如何使用定制化能力分级设计,完成全部的定制化需求。

    首先实现了一个 BottomModal 底部弹框组件,然后开始进行定制化设计。

    第一级定制化:定制主题变量

    “完成”文本的颜色,使用的是主题变量定义的品牌主色(Brand Primary Dark),beeshell 默认的品牌主色为黄色。

    通过组件库提供的自定义主题变量接口,可以修改品牌主色的色值,进而修改了“完成”文本的颜色。同理,可修改“取消”、“标题”的颜色。

    修改品牌主色,影响范围很大,所有组件的色彩风格统一变化。如果我只想把文本的颜色改成红色,但是并不想修改品牌主色,应该如何定制呢?可以使用第二级定制化。

    第二级定制化:提供定制属性

    这里提供 LabelText(类型为 String)和 LabelTextStyle(类型为 TextStyle)属性,如下图所示:

    图C-1

    代码实现为:

    <Text style={this.props.labelTextStyle}>{this.props.labelText || '完成'}</Text>

    LabelText 用于定制文案内容,将 LabelTextStyle 整体暴露出来,而不是只暴露颜色单个属性,这样有两点好处:

    • 开发者都熟悉 Style 这个名称与用法,但并不知道 xxxColor 是什么,组件更加易用。

    • Style 不仅可以定制 Color,还支持 fontSize、fontWeight 等属性,定制能力更强。

    以上两级主要是样式部分的定制,使用了样式优先级覆盖的策略,扩展样式(labelTextStyle)覆盖主题,主题覆盖内部样式。

    下一步,就是对于多行文字、图标的支持。

    第三级定制化:开放渲染区域

    提供 Label 属性,类型为 ReactElement 对象,任意定制 UI,如下图所示:

    图C-2

    到这里,区域以及区域内部的定制化需求,就都可以满足了。但是区域布局的定制化,因为布局情况太多,并不容易实现。如果再提供几个属性,用于定制布局方式、头部边框样式、底部按钮,按照这种方式,属性会无节制增加,势必造成组件难以维护,易用性也会大打折扣。那应该如何实现?我们设计了第四级。

    第四级定制化:继承/组合基类

    在 beeshell 1.0 的开源推广文章中也有讲到过,我们在组件库开发之初,对常见组件,进行了全面的梳理。在比较细的粒度,对组件进行拆分,以继承的方式,层层依赖,以功能渐进式增强的方式,实现各个组件。

    这样使得开发者,可以在任意层级上继承、组合组件,进行定制化开发,提供了极强的扩展能力。具体实现如下:

    首先,组件库实现一个 SlideModal 滑动弹框组件。这是一个比较底层、基础的组件,功能相对少,支持多个方向的滑动动画,内容完全由开发者自定义,通用性、定制化能力极强。实现效果如下:

    然后,组件库实现了 BottomModal 组件,继承 SlideModal,固定滑动的方向和开始位置,弹框内容横向拉伸至全屏、纵向自适应,功能增强而定制化能力减弱。实现效果如下:

    前文已经讲到,产品需求已经超出了 BottomModal 定制化的能力,强行实现只会带来不良后果。所以,我的方式是组合使用 SlideModal,开发一个新的组件,也就是第四级定制化。新组件的实现效果如下:

    第四级定制化,是使用了新的思路,不再盲目的增加一个组件的功能,来帮助开发者满足产品需求,而是提供了基础工具。基础工具实现了底层、复杂的部分,表现层的部分则让渡给开发者,用户自己实现,“授人以鱼不如授人以渔”。

    对比业界的开源 RN 组件库,也没有几个可以达到第四级的定制化能力。beeshell 通过四个级别的定制化的能力,可以轻松搞定所有的产品的需求。总之,定制化能力分级设计,是对定制化需求进行分类,针对每一类的定制化需求,设计了相应的策略,最终合成一个完整的解决方案,符合“关注度分离”的设计原则。

    功能丰富

    功能丰富旨在支持多种业务场景,支持特定业务场景的多种情况,进而提升组件库的易用性。功能丰富通过两个方面保证:组件丰富度、单个组件的功能丰富度。

    组件丰富度

    为了保证组件丰富度,我们通过多种渠道(既有组件整理、业务组件提取、参考标杆项目)来收集组件。最终规划了包括通用类、导航类、数据录入类、数据展示类、操作反馈类、基础工具在内的 6 个大类,共 50多个功能,旨在覆盖尽可能多的业务场景。

    目前,beeshell 已经实现了 38 个功能,与业界标杆项目的对比情况如下图:

    图K

    虽然,beeshell 的组件数量还比不上 Antd Mobile RN(用不了多久也会超过),但已经超过 NativeBase 和 React Native Element。beeshell在组件数量上有很大优势,可以支持更多的业务场景,且支持全部引入和按需引入,用户无需担心打包过多无用代码的问题。

    功能丰富度

    功能丰富度针对的是单个组件所支持的功能,旨在覆盖特定业务场景的多种情况。

    图O

    对于一个组件的功能丰富度,我们通过多方式收集、功能综合分析、UI 模型抽象、技术实现、验证反馈几个步骤来保证。

    • 多方式收集。通过多种方式来收集组件功能,收集方式包括:组件既有功能、业务新需求、标杆项目特性。

    • 功能综合分析。对收集的功能进行全面、综合分析与考虑,得出组件需要支持的功能特性。

    • UI 模型抽象。对组件功能进行抽象设计,根据 UI 模型,明确抽象设计的合理性和有效性。

    • 技术实现。根据平台特性、技术选型来完成代码实现。

    • 验证反馈。组件实现后,会应用到业务或者示例工程来验证,如果组件并不能满足需求,需要重复执行以上几步。

    下文以 SlideModal 组件的实现,举例说明:

    首先,通过多种方式,收集到的滑动弹框场景如下:

    图Q

    其次,综合分析得出,SlideModal 组件需要支持的功能有:弹出位置自定义、滑动方向自定义、全屏/半屏自定义。

    然后,进行 UI 模型抽象,抽象设计如下图所示:

    图N-1
    图N-2

    由 UI 模型得出,SlideModal 组件通过 (offsetX, offsetY) 坐标值来定义弹出位置;为了支持全屏/半屏效果,将屏幕分割为 4 个区域,分别自定义触控效果(阻断点击或者击穿);弹框内容在一个区域展示,每个区域有 3 种滑动方向(图 N-2),共支持 12 种滑动动效。

    最后,是基于 RN 平台的技术实现,并经过大量业务场景的验证反馈。SlideModal 组件的最终实现效果如下:

    对比业界开源 RN 组件库,针对滑动弹框场景,没有几个可以超过 SlideModal 的业务支持能力。

    SlideModal 组件只提供底层支持,如果要应用到真实的业务场景,还需要基于该组件进一步开发。beeshell 也提供了更高层次的定制组件,例如:Dropdown、Popover 等,可以直接使用。

    除了 SlideModal 之外,还实现了其他功能强大的组件:Slider 滑块组件,支持纵向和横向滑动;Rate 评分组件,实现一套滑动评分的机制,支持定制任意 UI 元素。由于篇幅有限,在此不再赘述。

    易用性提升

    组件易用性的提升,通过命名、文档和示例这三个方面来保证。

    命名

    命名包括组件名、属性与方法名。

    一个组件,实际上就是 Web 页面或者 App 中的元素、控件,通常因为原生控件的能力薄弱,而进行二次封装。所以组件名与原生控件名的名称,尽量保持一致。例如,Form 与 HTML Form 标签一致,Switch 从 iOS 控件 UISwitch 中得来。这样的命名,可以给与开发者更加直观的感受,通过名称就能知道组件大概的 UI 与功能,降低学习和使用的成本。

    属性与方法的命名,既要考虑原生控件的属性名,又要考虑组件库命名的一致性。例如,表单录入的相关组件,包括 Input、Radio、Checkbox、Switch 等,组件的值要统一使用 Value 命名,值变化的回调使用 onChange,选中状态使用 Checked 布尔类型。这样符合用户的直观感受,更加易用,降低使用成本。

    常用属性名举例如下:

    文档

    文档规定了统一的格式,旨在全方位介绍组件,方便开发者使用,格式内容如下:

    • 组件名称。

    • 组件描述。

    • 引入方式,包括全部、按需两种引入方式。

    • 示例演示,动图与静图。

    • 示例代码,使用伪代码,言简意赅,能说明使用方式即可,同时,附有完整示例代码的链接。

    • API 说明,分成 Props 和 Methods 两部分。

      • Props 包含 Name | Type | Required | Default | Description。

      • Methods 格式借鉴 RN 官方文档格式。

    示例

    beeshell 组件库遵循“关注度分离”的设计原则,对组件进行细粒度的拆分,进行了分类、分层,以及基础工具与组件实现的功能解耦。

    这些设计虽然大大提升了组件库的灵活性,但是在一定程度上,对开发者提出了更高的要求。开发者需要理解各个组件与工具,灵活的组合各个元素,才能更好的完成业务需求。

    为了方便开发者,更有效、合理的使用组件,我们将会实现一些经典的业务场景,以示例的形式开源出来。借助美团的平台服务,为用户提供在线演示的功能,用户可以下载美团 App(iOS 与 Android 都可以),扫描下图二维码,即可快速体验各种应用场景。

    测试

    代码开发的目标有两个:第一个是实现需求,第二个是保证研发质量。研发质量对公共组件库来说,尤为重要。测试是为了提高代码质量和可维护性,是实现代码开发第二个目标的一种方法。

    beeshell 1.0 中已经集成了“黑盒测试”与“白盒测试”。beeshell 2.0 在原有的基础上,进行了一定程度的优化,代码的可靠性与安全性,仍然保持最高级别,而测试覆盖率则由原来的 70% 提升到了 80% 以上,使用 SonarQube 的分析统计结果如下图:

    图A

    不仅如此,beeshell 2.0 在测试领域继续探索,集成了“灰盒测试”(基于开源方案 Detox 实现)。

    灰盒测试,是介于白盒测试与黑盒测试之间的一种测试,灰盒测试多用于集成测试阶段,不仅关注输出、输入的正确性,同时也关注程序内部的情况。灰盒测试不像白盒那样详细、完整,但又比黑盒测试更关注程序的内部逻辑,常常是通过一些表征性的现象、事件、标志来判断内部的运行状态。

    灰盒测试效果如下图所示:

    通过黑盒测试、白盒测试、灰盒测试,三种测试方案,更加全面的保证组件库的代码质量,大大提高了代码可维护性。

    开发调试

    受益于 MRN 平台,JS 代码与 Native 代码得以完全分离。

    beeshell 源码工程,包含了包括组件源码、示例代码、测试文件在内的全部 JS 代码,Native 部分则只负责打包生成容器(本文以美团 APP 举例说明),通过下载并安装 .app(iOS)或者 .apk(Android) 文件至模拟器,直接加载本地服务提供的 jsbundle,快速进入开发调试。

    前端开发再也不用关心 Native 的部分,无需耗时耗力的维护 Native 环境、依赖,极大降低了前端开发成本。开发调试流程如下图所示:

    图P

    未来规划

    我们的目标是把 beeshell 建设成为一个全面且完备的组件库,不仅会不断丰富 JS 组件,而且会不断加强复合组件去支持更多的底层功能。

    我们为组件库发展规划了三个阶段:

    • 第一阶段,beeshell 1.0 版本,开源 20+ 组件,主要提供基础功能。

    • 第二阶段,对我们在开发 React Native 应用几年时间积累的组件进行整理,同时参考业界的标杆项目,开源 50+ 组件。

    • 第三阶段,调研移动端 APP 常用的功能与场景,分析与整理,然后在 beeshell 中实现,开源 100+ 组件。

    此次 beeshell 2.0 升级,共计开源 38 个功能,而且已经详细的规划了另外的 15+ 组件,也会在近期开源,目前处于第二阶段的收尾阶段。

    第三阶段的建设,也在紧锣密鼓的筹备当中,要实现 100+ 组件任务十分艰巨,希望大家踊跃参与,共同建设。

    开源相关

    Github 地址:beeshell

    核心贡献者

    小龙泽楠,轶超,宋鹏,孟谦

    参考资料

    团队介绍

    外卖事业部终端团队,负责的多个终端和平台直接连接亿万用户、数百万商家和几万个运营人员,目标是在保障业务高稳定、高可用的同时,持续提升用户体验和研发效率。

    • 在用户方向上,构建了全链路的高可用体系,客户端、Web前端和小程序等多终端的可用性在99%左右;跨多端高复用的局部动态化框架在首页、广告、营销等核心路径的落地,提升了30%的研发效率;

    • 在商家方向上,从提高进程优先级、VoIP Push拉活、doze等方面进行保活定制,并提供了Shark、短链和Push等多条触达通道,订单到达率提升至98%以上;

    • 在运营方向上,通过标准化研发流程、建设组件库和Node服务以及前端应用的管理与页面配置等提升10%的研发效率。

    ----------  END  ----------

    招聘信息

    外卖事业部终端团队是由一群活力四射,对技术饱含热情,平均年龄不超过26岁的人共同组成。在这里,你可以看到大家对技术的追求,对产品的雕琢,对团队的认同,一切都是那么自然;在这里,你可以做一个纯粹的FE,写写JS,打磨一下CSS;也可以做一个精致的猪猪女孩(男孩),优雅的调试 Android 和 iOS 代码。当然,你绝对可以做一个霸道总裁,肆意的拥抱跨端方案,Hybrid,Flutter,React Native 等,都是你的新战场。我们正在持续努力成为一个面向未来编程的团队,而这里还缺一个你。欢迎志同道合的同学发送简历到:tech@meituan.com邮件标题注明:外卖事业部终端团队)。

    也许你还想看

    beeshell:开源的 React Native 组件库

    React Native工程中TSLint静态检查工具的探索之路

    函数式编程在Redux/React中的应用

    展开全文
  • 该项目起始于2017年初,当时公司主要技术栈为gulp+angular,鉴于react的火热的生态,在公司决定研发bss管理系统时选用react开发,目的也是为react native打下基础,以解决后期公司大前端技术栈的逐步成熟。...
  • yarn add antd less less-loader babel-plugin-import 需要配置的 less 和 babel依赖 第二步 yarn eject 暴露出 react webpack配置 上面暴露 webpack命令时 请查看下自己当前目录或上层目录是否有为提交的git ...
  • React 脚手架 create-react-app 新版使用说明 重点是配置代理 近期更新了一下 create-react-app 工具,然后发现,和原来的老版本使用出现了略微的差异。比如原先想要处理 sass 还需要去手动配置 webpack 但是新版...
  • Why Webpack? 兼容 CommonJS & AMD & ES6 模組規範 Bundle 效率高 ...編譯 sass, less 將資源 (css, img, font…) 包入 JS 內 JS 程式碼分散封裝 可用的擴充 plugin 很多 Install webpack 直接...
  • 采用react-highcharts来集成highcharts,效果图: 第一步:安装react-highchartscnpm i --save-dev react-highcharts第二步:在文件头部引入相应的组件:var ReactHighcharts = require('react-highcharts');第三步...
  • 技术资料 官方权威 ReactReact官方学习文档·英文】 React-中文文档【React官方学习文档·中文 中国程序员的福利...ReactNativeReactNative Github地址】 react-router【React 路由框架 Github地址】 Redux...
  • 作者简介琨玮,携程高级前端开发工程师,从事React Native/Web前端的开发及维护工作,喜欢研究新技术。在较大规模的前端项目中,测试对于保证代码质量十分重要,而React的组件...
  • 由于React的生态极为庞大,本文内容部分来自一些别人的汇总,至于原文只要还是能找到的,我都会贴上地址,谢谢前期贡献的作者,如果有没有被汇总到的,欢迎在下面补充。 生态圈: React官方推荐超大型项目使用的...
  • dva+react+ant.design

    2018-04-27 16:23:39
    之前在工作中使用了了react+redux。近来发现有个叫dva的东西出来。所以用自己能理解的解释下dva是什么东东基于 redux、redux-saga 和 react-router 的轻量级前端框架特性易学易用:仅有 6 个 api,对 redux 用户尤其...
  • import React, { Component } from 'react'; import CSS from './index.module.less'; import Swiper from 'swiper'; class MenuBar extends Component { constructor(props) { super(props); ...
  • 目录引言Web前端前端三座大山后端中的前端障碍为什么学习前端?为什么要学习React?正文1. CSS布局2. JavaScript ES63. Npm/Yarn4. 脚手架5.... React-Native12. Webpack 引言 Web前端 本文讨论的...
  • 前言总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应用的过程。 代码地址:React全家桶实现一个简易备忘录 原文博客...
  • 总括:本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应用的过程。 代码地址:React全家桶实现一个简易备忘录 原文博客地址...
  • GitChat 作者:Yeh. 关注微信公众号:GitChat 技术杂谈 ,一本正经的...随着React的发展和流行,围绕着React的生态系统已经逐渐形成。因此,本文通过描述每个知识点解决了哪些问题的方式来为大家介绍React生态系统。
1 2 3 4 5 ... 20
收藏数 816
精华内容 326
关键字:

native集成less react