PatrickStarsFromScnu / mini-program-by-mina

小程序原生mina框架开发
0 stars 0 forks source link

项目总结 #1

Open HolyZheng opened 6 years ago

HolyZheng commented 6 years ago

寻寻被试

HolyZheng commented 6 years ago

寻寻被试小程序端

1. 登录

采用官方提供的登录能力来获取用户标识,通过JWT(json web token)来保持登录状态,流程图:

  1. 通过调用wx.login拿到code(有效期为5分钟),发送给后端。
  2. 后端拿到code后,通过code,appSecret和appId请求微信接口服务接口,获取到session_key和用户唯一标识openid。查看数据库是否已存在该openid,如果不存在的话,将openid和用户信息绑定在一起存到数据库。
    `https://api.weixin.qq.com/sns/jscode2session?appid=
    ${option.appId}&secret=${option.appSecret}
    &js_code=${body.code}&grant_type=authorization_code`
  3. 生成token,返回给前端。
  4. 前端每次请求带上token,后端进行验证。

    具体实施

  5. wx.login
    wx.login({
    success: res => {
    res.code
    },
    fail: err => {
    / ...
    }
    })
  6. 后台获取session_key和openid,查看数据库是否已存在该openid,如果不存在的话,将openid和用户信息绑定在一起存到数据库。
    
    // https模块
    const https = require('https')
    // 封装请求方法
    const request = (url) => {
    return new Promise((resolve, reject) => {
    https.get(url, res => {
      let data = ''
      res.on('data', chunk => {
        data += chunk
      })
      res.on('end', () => {
        resolve(JSON.parse(data))
      })
    }).on('error', err => {
      reject(err)
    })
    })
    }

const url = https://api.weixin.qq.com/sns/jscode2session?appid= ${option.appId}&secret=${option.appSecret} &js_code=${body.code}&grant_type=authorization_code

const login = async ctx => { try { const res = await request(url) let user = await users.find({ where: { open_id: res.openid } }) // 如果不存在该用户,向users表添加该用户 if (!user) { await users.create({ name: body.userName, picture: body.picture, open_id: res.openid }) // 获取该user user = await users.find({ where: { open_id: res.openid } }) } } catch (err) { // err } } export login

4. 使用密钥生成token
```js
import jsonwebtoken from 'jsonwebtoken'
// 封装方法
const signToken = payload => {
  const token = jsonwebtoken.sign(
    payload,
    config.auth.jwtSecret,  // 存放着openid
    {
      expiresIn: '24h'
    }
  )
  return token
}
  1. 对请求验证token
    // 请求头带上token
    headers: {
    'Authorization': 'Bearer ' + token
    }
    
    import jsonwebtoken from 'jsonwebtoken'
    // 封装方法
    const checkToken = ctx => {
    const token = ctx.header.authorization.split(' ')[1]
    try {
    jsonwebtoken.verify(token, config.auth.jwtSecret, {
      expiresIn: '24h'
    })
    } catch (err) {
    throw new Error('token验证错误:' + err)
    }
    }

// 注册中间件 app.use(jwt({ secret: config.auth.jwtSecret }).unless({ // 除开登录请求 path: [/\/login/] }))

[两种常用的认证方式](https://github.com/HolyZheng/holyZheng-blog/issues/9#issuecomment-402698538)
### 2. 优化
#### 图片懒加载
小程序image组件,自带lazy-load属性,但是为什么我还是选择自己实现懒加载呢,因为自带的懒加载并不是当图片出现在可视区域的时候再去加载,经过实践发现,它会提前两个屏幕的高度进行加载,也就是说你的图片数量多到超过可视范围的两个屏幕高度才能得到懒加载的好处,这个提前量太多了,所以有必要自己实现一个“真正的”懒加载。
```js
    const ctx = this
    let className = '.brief-image-' + ctx.properties.index
    wx.createIntersectionObserver().relativeToViewport().observe(className, res => {
      if (res.intersectionRatio > 0) {
        ctx.setData({
          load: true
        })
      }
    })
  1. 通过createIntersectionObserver方法创建一个IntersectionObserver对象,小程序里面给它的定义是节点布局交叉状态API可用于监听组件节点在布局位置上的相交状态。
  2. 通过relativeToViewport方法,接收一个选择符作为参数来规定相对的节点,默认为页面可视区域
  3. 再通过observe方法,接收一个选择符作为参数来指定监视的节点。
  4. 在回调中判断res.intersectionRatio是否大于 0 来判断始出出现在了可视区域。
    • 数据分组下载 通过limitoffset参数来分组(分页)请求数据。
      // 后端
      Experiments.findAll({
      offset: parseInt(body.offset),
      limit: parseInt(body.limit),
      include: [{
      model: Users
      }]
      })
HolyZheng commented 6 years ago

后端

为什么选择koa2,与koa,express不同?

Express 优点:线性逻辑,通过中间件形式把业务逻辑细分、简化,一个请求进来经过一系列中间件处理后再响应给用户,清晰明了。 缺点:基于 callback 组合业务逻辑,业务逻辑复杂时嵌套过多,异常捕获困难。

Koa 优点:首先,借助 co 和 generator,很好地解决了异步流程控制和异常捕获问题。其次,Koa 把 Express 中内置的 router、view 等功能都移除了,使得框架本身更轻量。 缺点:社区相对较小。

koa2 采用async/await

  1. 首先我为什么不选择express呢?express在我眼中最大的缺点便是它采用callback的方式来处理异步操作,这种方式当我们的业务比较复杂的时候呢,就容易出现类似回调金字塔的现象,就会有可读性差,可维护性差,一场捕获困难等问题。所以我没有选择express
  2. 我为什么选择koa2而不选择koa1呢,因为koa1采用generator来处理异步,而koa2采用更合适的async/await特新来处理异步。
  3. generator本身是一个特殊的函数,可以暂停和重暂停的地方重新开始执行,通过function*来声明一个generator,通过yield来作为暂停的标志并返回跟在后面的表达式的值。在异步场景中需要我们在异步操作有了结果之后去调用generator实例的next()方法,让函数继续往下执行。
  4. 而async/await它更适合处理异步操作,它具有更好的语义,内置了执行器,返回promise,await后面可以跟promise和其他原始类型(数值、字符串和布尔值),通过跟promise配合呢,我们可以完全像处理同步操作一样去处理异步操作,通过try...catch...去捕获错误,十分方便。所以我选择了koa2

关注点分离

关注点分离就是说我们的应用程序中的每个模块或逻辑层只应该对一件事情负责,而不应该包含处理其它事情的代码。分离关注点的做法就是把一个大型的应用程序划分为多个稍小一些的功能单元,从而减少代码的复杂度,跟耦合度。 这里呢将我们的代码分为了:controllers、models、routers、services层

routers层 分发来自前端的请求到对应的controller中

controllers层 处理分发进来的请求,调用对应的services接口处理数据

service层 提供对数据库的直接操作

models 通过ORM抽象成对象,对数据的实体定义

RESTful形式

REST全称是Representational State Transfer,一种软件架构风格、设计风格,而不是标准。 把请求都当作资源来看代,而不是动作。 看Url就知道要什么 看http method就知道干什么 看http status code就知道结果如何

所以我们应该用名称去命名我们的资源,而不是动词,而且一般使用复数。 需要合理的适合http方法,比如,get获取数据,post提交数据进行处理并获取相应的资源,put提交数据去替代指定资源,delete删除资源等。

  1. 合理使用http method
  2. 合理命名资源,名词而不是动词,复数而不是单数,小写而不是大写
  3. 合理使用http状态码

什么是中间件

中间件相当于插件,用于扩展我们的功能。

洋葱模型

洋葱模型1 洋葱模型2

// #1
app.use(async (ctx, next)=>{
    console.log(1)
    await next();
    console.log(1)
});
// #2
app.use(async (ctx, next) => {
    console.log(2)
    await next();
    console.log(2)
})

app.use(async (ctx, next) => {
    console.log(3)
})
1
2
3
2
1
HolyZheng commented 6 years ago

Nginx

Nginx是一个高效的web和反向代理服务器。 与Apahce对比:

异步非阻塞:在处理异步任务的时候,当前线程不会被挂起,而是继续执行。相对的阻塞就是,执行异步任务的时候当前任务会被挂起,知道任务得到了结果再继续执行该任务。

部署nodeJS项目

// 配置一个要代理的服务器
upstream rockjins {
    server 127.0.0.1:8081; 
# 这里的端口号写你node.js运行的端口号,也就是要代理的端口号,我的项目跑在8081端口上
}

// 配置一个服务器,将这里的请求分配到rockjins
server {
    listen 80; #这里的端口号是你要监听的端口号
    server_name 39.108.55.xxx www.rockjins.com rockjins.com; # 这里是你的服务器名称,也就是别人访问你服务的ip地址或域名,可以写多个,用空格隔开

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-Nginx-Proxy true;
        proxy_set_header Connection "";
        proxy_pass http://rockjins; # 这里要和最上面upstream后的应用名一致,可以自定义
    }
}
HolyZheng commented 6 years ago

文件上传

关键词:formData对象,content-type:multipart/form-data

同input标签设置input标签的type属性为file,去提取本地文件,然后我们拿到的文件是一个二进制对象,如果文件比较大就每次通过slice方法去提取部分来分块上传,通过formData对象,用键值对模拟表单数据上传文件,并把content-type设为multipart/form-data以二进制的方式来传输内容。

formdata

通过formData对象,用键值对模拟表单数据,让我们可以直接通过JavaScript去发送表单数据。

content-type

设置请求头的content-type为:multipart/form-data,该content-type是以二进制的形式来传输文件,那样我们就可以传输各种类型的文件了。 与application/x-www-form-urlencoded的区别,此类型会以键值对的方式来传输。会添加到url上,组成url的参数部分(?parameters)

multipart/form-data

POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"

title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

通过一个boundary 来划分请求头和请求体。

ps:file api还有 lastModified对象所引用文件最后修改时间, 自 1970年1月1日0:00 以来的毫秒数。lastModifiedDate对象所引用文件最后修改时间的 Date 对象。name文件名称。size文件大小。type文件的类型。

蹦床函数优化

  1. 首先呢,这里涉及一个尾递归优化的概念。因为在我们的JavaScript中,函数的调用会形成一个“执行环境”用来保存函数的调用位置和内部变量等信息,然后执行环境是由“环境栈”来管理的,每当当前执行环境中遇到一个新的函数调用,就会形成一个新的执行环境添加到环境栈的顶部。知道函数执行完毕,对应的执行环境才会消失。然后递归呢,他会在函数自身内部调用自身,当递归的次数达到一定数量级后,对应环境栈的深度是很可怕的,不仅会对性能造成一定的影响,而且还可能出现“栈溢出”的错误。
  2. 所以呢,为了避免这样的问题,es6引入了尾递归优化的特性,尾递归优化基于尾调用优化,他的做法就是把函数调用放到当前函数的最后一步来执行。因为是最后一步的原因,它不再需要保存外部的执行环境,而是用新的执行环境替代当前的执行环境,所以经过尾递归优化后,对应的环境栈始终只有一层。从而提高了性能和避免了栈溢出的现象。
  3. 但是在尝试过程中发现实际落实了尾递归优化的浏览器并不多,在chrome,edge,firefox都发生了栈溢出的现象,所以就觉得使用了一个蹦床函数来模拟尾递归的效果。
    function trampoline(f) {
    while (f && f instanceof Function) {
    f = f();
    }
    return f;
    }
  4. 蹦床函数就是用while循环做一个循环判定,只要获取到的返回值是函数,就继续执行获取到的函数,并获取它的返回值,如果获取到的值不是函数了,就返回该值。
  5. 然后将我们的修改我们的尾递归,将最后一步递归执行改为返回一个新函数。然后将我们修改过后的尾递归作为参数传递给蹦床函数执行。
  6. 因为我们的每次执行都会返回一个新的函数,然后新的函数会在蹦床函数中的新的一轮循环中执行,所以 对应环境栈始终保存为一层,就达到了模拟尾递归优化的效果。
HolyZheng commented 6 years ago

webpack配置vue开发环境

整个配置分为三个部分:基本配置,开发环境配置,生产环境配置。

基本配置

基本配置,是一些开发环境和生产环境都会用到的配置,比如配置babel,图片处理转base64等。

const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
// vue-loader 需要 配置此插件才能生效(有点懵)
const CleanWebpackPlugin = require('clean-webpack-plugin')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports = {
  entry: {
    app: path.resolve(__dirname, '../src/main.js')
  },
  resolve: {
    extensions: ['.js', '.vue'],
    alias: {
      "@": resolve('src')
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
        include: [path.resolve(__dirname, '../src')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: 'images/[name].[hash:7].[ext]'
            }
          }
        ]
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: 'fonts/[name].[hash:7].[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new CleanWebpackPlugin(['dist'], {
      root:  path.resolve(__dirname, '..'),
      exclude:  [],
      verbose:  true,
      dry: false
    })
  ]
}

开发环境,配置一个本地服务,指定mode:development,用vue-loader处理.vue文件,用vue-style-loader,css-loader处理.vue文件中的css代码等。

const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 每次都创建新的html确保引用的文件名正确
var baseWebpackConfig = require('./webpack.base.config')

module.exports = merge(baseWebpackConfig, {
  devServer: {
    port: 8000,
    // 0.0.0.0 可使得我们用内网ip访问,这样可以用其他电脑或手机连接自己电脑进行访问
    host: '0.0.0.0',
    overlay: {
      // 将错误显示到浏览器页面上
      errors: true
    }
  },
  output: {
    publicPath: '/',
    // 在dev阶段不需要输出,所有不需要设置路劲
    // 通过htmlWebpackPlugin使得index.html中script src的值与生成的js文件路径保持一致
    filename: 'js/[name].[hash:7].js'
  },
  mode: 'development',
  module: {
    // .vue文件中使用css需要css-loader
    // vue-style-loader是vue-loader的dependencies
    // css-loader 是 vue-loader的peerDependencies
    rules: [
      {
        test: /\.vue$/,
        use: ['vue-loader']
      },
      {
        test: /\.css$/,
        use: ['vue-style-loader', 'css-loader']
      },
      {
        test: /\.s[ac]ss$/,
        use: ['vue-style-loader', 'css-loader', 'sass-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html',
      // 路径相对于 Webpack 配置项 output.path
      template: 'index.html'
      // 模板,在此模板上做修改
    })
  ]
})

生产环境,指定mode:production,他的作用是自动帮我们定义环境变量为production,自动启用代码压缩,我们不再需要通过webpack.DefinePlugin去配置环境变量的值,也不需要在使用uglifyjs-webpack-plugin去进行代码压缩,还要通过vue-loader处理.vue文件,还要配置css代码提取,和自动添加前缀之类的,最后还需要配置一个公共代码的提取。

const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
// 提取.vue文件中的css

const baseWebpackPlugin = require('./webpack.base.config')

// 通过postcss和cssnano对提取出来的css进行处理
// 这里好像还有点问题
var postOptions = {
  plugins: [
    require('cssnano')({
      sourceMap: true,
      autoprefixer: {
        add: true,
        browsers: ['> 5%']
      }
    })
  ]
}

module.exports = merge(baseWebpackPlugin, {
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'js/[name].[chunkhash:7].js'
  },
  devtool: 'source-map',
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: {
          loader: 'vue-loader',
          options: {
            loaders: {
              // 对.vue中的css做处理,并提取出来, posstcss-loader
              // 需要放在css-loader后面预处理器loader前面
              css: ExtractTextPlugin.extract({
                use: [
                  'css-loader', 
                  {
                    loader: 'postcss-loader',
                    options: postOptions
                  }
                ]
              }),
              scss: ExtractTextPlugin.extract({
                use: [
                  'css-loader', {
                    loader: 'postcss-loader',
                    options: postOptions
                  }, 
                  'sass-loader'
                ]
              }),
              sass: ExtractTextPlugin.extract({
                use: [
                  'css-loader', {
                    loader: 'postcss-loader',
                    options: postOptions
                  }, 
                  'sass-loader'
                ]
              })
            }
          }
        }
      },
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          use: ['css-loader', {
            loader: 'postcss-loader',
            options: postOptions
          }]
        })
      },
      {
        test: /\.s[ac]ss/,
        use: ExtractTextPlugin.extract({
          use: ['css-loader', {
            loader: 'postcss-loader',
            options: postOptions
          }, 'sass-loader']
        })
      }
    ]
  },
  mode: 'production',
  plugins: [
    // 作用域提升插件(还不太清楚在这里的作用)
    // new webpack.optimize.ModuleConcatenationPlugin(),
    // 定义环境变量,以便Uglify自动删除代码内的警告语句
    // new webpack.DefinePlugin({
    //   'process.env.NODE_ENV': JSON.stringify('production')
    // }),
    // 创建新的html
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      title: '智传'
    }),
    // webpack4 的代码压缩可通过指定mode为production来实现
    // 故不需要再引入插件
    new ExtractTextPlugin({
      filename: 'css/style.[hash:7].css'
    }),
    // new UglifyjsWebpackPlugin()
  ],
  // 优化 将公共代码提取到commons文件中,减少带宽的浪费。
  //将node_module中(几乎不会变动的)的代码提取,到vendor中,以便浏览器缓存。
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          chunks: 'initial',
          name: 'commons',
          minChunks: 2,
          maxInitialRequests: 5,
          minSize: 0
        },
        vendor: {
          chunks: 'initial',
          test: /vue/,
          name: 'vendor',
          priority: 10,
          enforce: true
        }
      }
    },
    runtimeChunk: true
  }
})

总结: 使用了webpack4进行配置,整个配置过程分为三个部分:基本配置,开发环境配置,生产环境配置。

  1. 基本配置主要是一些开发环境和生产环境都用到的一些基本的配置。比如给js配置babel,图片转base64等基本的配置。
  2. 开发环境的配置主要是配置一个本地服务器以及.vue文件的解析,vue-loader处理.vue文件,vue-style-loader,css-loader处理.vue文件中的css代码。还要指定mode: development,它的作用主要是优化了构建速度和开发体验,自动帮我们指定process.env.NODE_ENV 的值为development,不再需要我们通过webpack.DefinePlugin去配置环境变量的值。(我们使用的大部分库都会根据环境变量的值做相应的调整,所以需要我们去设置环境变量的值去启用这部分优化),最后通过webpack-merge将基本配置的内容合并进来。
  3. 生产环境的配置,主要是配置.vue文件的解析,和css代码的提取以及自动添加前缀,还有就是代码分离和公共代码提取,还要将mode: production,它的作用在于自动帮我们将环境变量设置为production,优化打包速度,自动启用代码压缩,自动配置变量提升。(不再需要我们通过webpack.DefinePlugin去设置环境变量,不再需要我们使用uglifyjs-webpack-plugin去压缩代码,不再需要我们配置ModuleConcatenationPlugin)最后将基本配置通过webpack-merge合并进来。
HolyZheng commented 6 years ago

HOZ总结

这是一个自己写得一个mvvm demo,主要是对一些原理的实践,比如对vue响应式数据绑定原理,flux架构,virtual dom机制的一个实践。

响应式原理

vue2.0响应式数据绑定原理 在这个库中呢,更数据绑定有关的代码集中在src/core/compile.js文件以及src/storeux/createStore.js中,通过数据劫持和观察者模式来实现。

  1. 首先,在createStore.js文件中,通过observer方法遍历我们的每一个数据,为每一个数据执行defineReactive方法,在defineReactive方法中通过Object.defineProperty方法去修改每一个数据的getter和setter。
  2. 然后呢,在compile.js文件中,会通过compile方法对生成的virtual dom进行一次遍历,为virtual dom中每一处使用到了数据的地方创建一个watcher对象,这个watch对象就是我们观察者模式中的观察者,在创建的同时会把当前的watcher添加到一个全局变量Dep.target中。
  3. 然后会触发对应数据的getter属性,会在getter中将当前Dep.target中的watcher添加到对应的Dep中,Dep相当于观察者模式中的目标对象,每一个数据对应一个Dep,一个Dep管理多个watch。
  4. 到此为止完成了依赖收集的工作。
  5. 当我们的数据被修改的时候呢,对应数据的setter会被触发,会调用对应Dep的notify方法发布通知,调用Dep中每一个watcher的update方法,去获取最新的数据。
  6. 这就是这个库中的响应式数据绑定。

flux架构

首先什么是flux呢?

flux是一种架构模式,他的核心理念在于以一种单向数据流的方式来管理数据。它有几个关键的概念:

所有的应用状态在store中同一管理,视图层不允许直接修改应用状态,只能触发action,在dispatcher中监听action并执行相应的操作修改store中的数据。

flux架构有什么好处呢?

随着应用的日益复杂,数据量的增大,如果不对数据进行相应的管理,管理不断变化的状态,是非常困难的。状态在什么时候,由于什么原因,发生了怎样的变化都难以观察。 通过我们的flux架构:

  1. 视图层变得很薄,只包含渲染逻辑和触发action
  2. 要理解一个store可以发生的变化,只需要看注册的actions回调就行
  3. 状态变化通过action触发,action通过dispatcher,所以每一次改变都从一个地方流过,强制的让所有的状态变化都必须留下一笔记录,这样就可以利用这个来做各种 debug 工具等。

hoz中的flux

这部分代码主要集中在src/sroreux/createStore.js文件下,这里定义了一个createStore接受我们的应用状态和一个changeState方法为参数。

  1. 这里的changeState相当于flux中的dispatcher,由用户定义对应用状态的各种操作。
  2. 然后定义了一个isDispatch标识,用来判断对应用状态的修改是否是通过dispatch来发起的,
  3. 然后定义了一个dispatch方法用来发起action,dispatch方法会先将isDispatch标识设为true,然后将应用状态和action作为参数调用changStore方法,在changeState中修改对应的应用状态,然后对于每一个数据,还会用object.defineProperty去修改它的setter,在setter中先判断isDispatch标识是否为true,不是则报错。然后修改完数据后就把isDispatch重新修改为false。从而强制用户必须通过dispatch发出action来修改应用的状态。
HolyZheng commented 6 years ago

nativescript-vue

这个项目,nativescript本身也是一个跨平台开发框架,支持使用angular或js或typescript来跨平台开发原生应用,但是热度并不高。我也没有特别的关注它。然后有一天,突然发现它新增了一个选项,就是nativescript-vue,可以使用vue来进行开发了。于是就凑了一下热闹。搞了个demo。

原理

nativescript的原理和react-native有点相似:提供一个运行环境,提供一个JavaScript引擎,android端是V8引擎,ios端是JavaScriptCore引擎,一个虚拟机上运行JavaScript代码,都可以调用平台api和ui组件。而nativescript更专注于创建一个与平台无关的单一的开发体验,react-native则是抽象业务逻辑的同时,支持每个平台UI呈现固有的差异,并把重心放在高性能的渲染和执行上面。