课程目标
- 学习Rax⼩程序的基本语法、API及组件的使⽤;
- 掌握Rax⼩程序的⾼阶⽤法;
知识要点
Rax基本使⽤
- Rax 语法层⾯以 React 为标准,可以使⽤ Hooks、Context 等 80% 以上⽀持度的 React API
- 官⽅配套的研发框架 Rax App,⽀持 TypeScript、Less/Sass 等基础⼯程能⼒,同时⽀持 MPA、 SPA、SSR
- ⽀持通过完整的 Rax 语法开发跨⽀付宝/微信/字节等不同⼚商的⼩程序,同时可降级到 Web
- 基于 Web 标准⽀持跨多容器的跨端应⽤,包含 Web 应⽤、Flutter 应⽤(Kraken)、Weex 应⽤
- 丰富的跨端⽣态,⽐如跨端组件 Fusion Mobile,跨端 API Uni API
- Rax 与 React 的区别是什么?
- Rax ⾯向多端设计的,从最初始就引⼊了 Driver 机制来适配不同端,相⽐ React 更加轻量, gzip 之后只有 6KB;
- Rax 对于 React 的 API ⽀持度是怎样的?
- 不⽀持 Suspense、lazy API,其他诸如 Hooks、Component 等 API 都⽀持;
⽬录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ├── .rax/ // 运⾏时⽣成的临时⽬录,git不要提交 ├── build/ // 构建产物⽬录,npm run build 后产物 ├── public // 本地静态资源 │ └── favicon.png ├── src │ ├── app.json // 路由及⻚⾯配置, routes、window等 │ ├── app.ts // [⼩程序|SPA]应⽤⼊⼝ │ ├── miniapp-native/ // [⼩程序]⼩程序原⽣代码 │ ├── components/ // ⾃定义业务组件 │ ├── pages/ // ⻚⾯ │ ├── models/ // 应⽤级数据状态 ├── build.json // ⼯程配置 ├── package.json └── tsconfig.json
|
环境配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
export default { default: { appId: '123', baseURL: '/api' }, local: { appId: '456', }, daily: { appId: '789' }, prod: { appId: '101' } } import { config } from 'rax-app'; console.log(config.appId);
|
编写组件
⽀持
- Function Component
- Class Component
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { createElement } from 'rax';
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
import { createElement, Component } from 'rax'; class Welcome extends Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
|
支持类型:
- props
- state
- Fragment
- Hooks:常⽤Hooks:useState、useEffect etc,具体看下⽂
- JSX
- JSX+
- 条件判断:x-if、x-elseif、x-else
- 循环列表:x-for
- 单次渲染:x-memo:⾸次render后值不变不会render
- 插槽:x-slot
- 类名绑定:x-class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <View x-if={condition}>Hello</View> <View x-elseif={anotherCondition}></View> <View x-else>NothingElse</View>
{} <tag x-for={item in foo}>{item}</tag>
<tag x-for={(item, key) in foo}>{key}: {item}</tag>
<p x-memo>this paragragh {mesasge} content will not change.</p>
<Waterfall> <view x-slot:header>header</view> <view x-slot:item="props">{props.index}: {props.item}</view> <view x-slot:footer>footer</view> </Waterfall>
<slot name="header" />
<div x-class={{ item: true, active: val }} />
|
样式⽅案
- 全局样式
统⼀定义在 src/global.[css|less|scss] ,框架会⾃动引⼊;
- 组件样式
⽂件名定义:xxx.module.[css|less|scss],使⽤ CSS Modules ,避免全局污染
1 2 3 4 5
| .container { background: #fff; width: 750rpx; }
|
1 2 3 4 5 6 7 8
| :global { body { a { color: blue; } } }
|
1 2 3 4 5 6 7 8 9 10 11
| import styles from './index.module.css'; function Home() { return ( <View className={styles.container}> <View>CSS Modules</View> </View> ); }
<View class="container--1DTudAN">title</View>
|
- 内联样式
在 build.json ⾥配置了 inlineStyle: true 则说明整个项⽬使⽤内联样式
1 2 3 4 5
| const myStyle = { fontSize: '24px', color: '#FF0000' }; const element = <View style={myStyle}>Hello Rax</View>;
|
⽀持在使⽤内联样式⽅案的项⽬中局部⽀持⾮内联形式
在 build.json 中将 inlineStyle 设置为 { forceEnableCSS: true }
1 2 3 4 5 6 7
| import cssModule from './index.module.css'; import styles from './index.css'; function App() { return <div className={cssModule.header} style={styles.header} />; }
|
框架API
通过 rax-app 中引⼊
import { runApp } from 'rax-app';
- runApp
- APP_MODE
- ErrorBoundary
- store
- getHistory:获取history实例
- getSearchParams:获取参数
- history:路由实例
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { history } from 'rax-app';
console.log(history.length);
console.log(history.action);
console.log(history.location);
history.push('/home');
history.replace('/home');
history.goBack();
|
安全区域适配
- 刘海屏适配为了使⻚⾯顶部内容,不被刘海遮挡,可以通过设置容器节点的 padding-top 值来实现,使核⼼内容整体下移。
获取刘海⾼度,⾸先需要设置 viewport-fit ,调整可视窗⼝的布局⽅式。当且仅当 viewport-fit 设置为 cover 时,可以进⼀步设置⻚⾯的安全区域范围。
<meta name="viewport" content="width=device-width, viewport-fit=cover">
然后,结合 env() ⽅法,可以获取 safe-area-inset-top 值,并将其作为容器节点的 padding-top 值。
1 2 3 4
| .root { padding-top: constant(safe-area-inset-top); padding-top: env(safe-area-inset-top); }
|
- 底部⼩⿊条适配
有 tabbar 的应⽤,iPhone 底部的⼩⿊条常常会挡住 tabbar,影响其可操作区域。和刘海屏适配的原理 ⼀致,以 tabbar 为例,⼩⿊条适配可以通过调整 tabbar 的 padding-bottom 值,增加空⽩区域来实现。 
1 2 3 4 5
| .tabbar { padding-bottom: 0; padding-top: constant(safe-area-inset-bottom); padding-top: env(safe-area-inset-bottom); }
|
静态资源使⽤
将⽂件放⼊ public ⽂件夹,webpack不会处理它。⽽是它将被复制到构建⽂件夹中;
要引⽤ public ⽂件夹中的资源,需要使⽤名为 process.env.PUBLIC_URL 的特殊变量,这个值会根据⼯程配中的 publicPath 变化:
1 2 3
| render() { return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />; }
|
通常我们建议从 JS 导⼊ stylesheets,图⽚和字体存⼊public⽂件夹中:
- 你需要在构建输出中具有特定名称的⽂件,例如 manifest.json;
- 你有数千张图⽚,需要动态引⽤它们的路径;
- 你希望在打包代码之外包含⼀个⽆需⾛构建逻辑的⼩脚本;
- 某些库可能与 webpack 不兼容,只能将其放在 public 中引⼊;
代码切割
- dynamic import
使⽤ import(),webpack 会在编译阶段对引⼊的资源进⾏代码切割,即只有当运⾏时逻辑执⾏到 import() 调⽤点时才会加载对应的资源,该函数返回值是 Promise
1 2 3 4 5 6 7 8
| import { isWeb } from '@uni/env'; if (isWeb) { import('./fetch').then(fetch => { fetch('m.taobao.com'); }).catch(err => { console.error(' '); }); }
|
- rax-use-import
函数式组件提供的Hooks
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { createElement } from 'rax'; import useImport from 'rax-use-import';
export default function App() { const [Bar, error] = useImport(() => import( './Bar')); if (error) { return <p>error</p> } else if (Bar) { return <Bar /> } else { return <p>loading</p> } }
|
Rax⼩程序基本介绍
Rax ⼩程序以运⾏时⽅案为基础,⽀持局部场景使⽤编译时⽅案开发组件,充分结合了⼆者的优势特点,让⽤户在保证开发效率的⼤前提下能够针对局部场景进⾏更⾼渲染性能的优化。
Rax⼩程序简介
⽅案对⽐
- 编译时⽅案:
- 通过 AST 转译 + 运⾏时垫⽚模拟 Rax core 的⽅式,将Rax DSL 1:1 输出为原⽣⼩程序代码;
- 限制较多:
- JSX 较为灵活,适配⼯作量巨⼤,维护成本较⾼,开发者需要遵循⼤量的语法约束,否则代码就不能正常编译运⾏,开发效率难以保证;
- 需要配合runtime垫⽚来模拟 Rax 运⾏ –> Rax 有功能更新时,编译⽆法得到同步;
- DOM 和 BOM API 的缺失,Web 上累积的各种前端⽣态⽆法复⽤;
- 性能较好;
- 运⾏时⽅案:
- 通过底层模拟 DOM 和 BOM API,使开发者可以使⽤Rax DSL开发;
- 性能较差,但基本对⻬web端⽣态;
Rax运⾏
npm init rax rax-example
项⽬中 build.json 中的tagrets
1 2 3 4 5 6 7 8 9
| { "targets": [ "miniapp", "wechat-miniprogram", "bytedance-microapp", "baidu-smartprogram", "kuaishou-miniprogram" ] }
|
package.json
- start:rax-app start
- build:rax-app build
- 不保留注释;
- 去除source map;
- 去除开发⼯具;
rax-app webpack config github地址:
https://github.com/raxjs/rax-app/tree/master/packages/rax-webpack-config/src
入口文件
src/app.js(TS为app.tsx)为⽂件⼊⼝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { runApp } from 'rax-app'; const appConfig = { app: { appProvider: ({ children }) => { return <ConfigProvider>{children}</ConfigProvider>; }, errorBoundary: true, ErrorBoundaryFallback: <div>渲染错误</div>, onErrorBoundaryHandler: (error, componentStack) => { }, onLaunch() {}, onShow() {}, onHide() {}, onError() {}, onShareAppMessage() {}, }, store: { initialStates: {}, getInitialStates: (initialData) => {} } }
runApp(appConfig);
|
应用配置
在src/app.json 中,对window,tabbar配置,且⼩程序中的路由存在于routes中
- path:指定⻚⾯对应的路由地址
- source: 指定⻚⾯组件地址,必须写成
pages/[PAGE_NAME]/index 格式,暂不⽀持嵌套式路由;
- targets:指定⻚⾯需要构建的端,默认为
build.json 所配置的 targets 的值;
- window:指定该⻚⾯的窗体表现,可以覆盖全局的窗⼝设置;
- tabBar:如果应⽤是⼀个多 tab 应⽤(底部栏可以切换⻚⾯),可以指定 tab 栏及切换时显示的对应⻚⾯;
Tips:原⽣⼩程序中,app.json(debug、networkTimeout)的其他字段都可以在此配置
⼯程配置
在build.json中,其中可以指定⼩程序的配置项
- buildType:⼩程序引擎,默认位运⾏时,编译时为 compile;
- nativeConfig:即微信⼩程序的project.config.json;
- subPackages:是否分包加载;
- runtimeDependencies:在运⾏时引擎下,使⽤rax⼯程的多包npm时,使⽤编译时还是运⾏时实现;
- nativePackage:配置原⽣⼩程序⾃定义组件及原⽣⻚⾯所⽤到的 npm 依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| { "targets": ["miniapp", "wechat-miniprogram"], "miniapp": {}, // 支付宝小程序语法配置 // 微信小程序语法配置 "wechat-miniprogram": { "nativeConfig": { "appId": "YOUR_APP_ID", "miniprogramRoot": "build/wechat-miniprogram", }, "subPackages": { "shareMemory": true // 共享运行时内存 }, "nativePackage": { // 自动安装 Rax 项目中的元素小程序自定义组件及原生页面所用到的 npm 依赖 // 不配置 dependencies 字段且 autoInstall 未设置为 false 时会默认安装所有依赖,可作为性能优化 "autoInstall": true, "dependencies": { "mini-ali-ui": "^1.3.4" } } } }
|
Tips:
- 当运⾏时项⽬中使⽤到由 Rax 组件⼯程产出的多端组件 npm 包时,不配置 runtimeDependencies, 默认采⽤其编译时的代码实现。此时需要将其配置到 nativePackage.dependencies,否则产物中将报找不到该组件的问题;
- nativePackage.dependencies 中配置的原⽣⼩程序 npm 依赖依然需要在 Rax 项⽬根⽬录的 package.json 中配置并安装;
⻚⾯⽣命周期及事件处理
移除了部分原⽣⼩程序事件相关Hooks:
- useHistory、useReachBottom等只能作为单纯API⽽不是Hooks使⽤;
- 性能与可扩展性较弱,每当⼩程序新⽀持⼀个事件,都需要去开发⼀个对应功能的 API,并且在⻚⾯初始化之初就监听所有事件 –> 不合理的;
新增API:
| Syntax |
Description |
registerNativeEventListeners(Component,[...eventName]) |
注册该⻚⾯需要监听的所有事件,第⼀个参数为⻚⾯级组件 |
addNativeEventListener(eventName, callback) |
开始监听某个事件并执⾏回调函数 |
removeNativeEventListener(eventName, callback) |
移除某个事件的回调函数⾏回调函数 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { createElement, useEffect } from 'rax'; import View from 'rax-view'; import Text from 'rax-text'; import { isMiniApp } from 'universal-env'; import { registerNativeEventListeners, addNativeEventListener, removeNativeEventListener } from 'rax-app'; function Index() { function handlePageReachBottom() {} useEffect(() => { if (isMiniApp) { addNativeEventListener('onReachBottom', handlePageReachBottom); } return () => { if (isMiniApp) { removeNativeEventListener('onReachBottom', handlePageReachBottom); } } }, [])
return ( <View> <Text>1</Text> </View> ); }
if (isMiniApp) { registerNativeEventListeners(Index, ['onReachBottom']); } export default Index;
|
注意:
- onShow事件建议:
- Function component:使⽤
usePageShow(cb),cb在渲染后执⾏;
- Class component:使⽤
onShow(),会在 constructor 后调⽤;
addNativeEventListener 监听需要在 useEffect 外调⽤,也要在 useEffect cb 清除监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { createElement, useEffect } from 'rax'; import View from 'rax-view'; import Text from 'rax-text'; import { registerNativeEventListeners, addNativeEventListener, removeNativeEventListener } from 'rax-app'; function Index() { function handlePageShow() {} addNativeEventListener('onShow', handlePageShow); useEffect(() => { return () => { removeNativeEventListener('onShow', handlePageShow); } }); return ( <View> <Text>1</Text> </View> ); } registerNativeEventListeners(Index, ['onShow']); export default Index;
|
组件
在微信等⼩程序端通过 bind 前缀绑定事件,在 JSX 中需要处理为 on 前缀,并遵循驼峰式命名规则,如上⾯ bindgetphonenumber 处理为 onGetPhoneNumber;
使⽤⼩程序原⽣组件
- npm安装
- 配置nativePackage 的 dependencies,不然会安装所有依赖;
- 开发者个⼈npm包
- 配置在 package.json 中的miniappConfig,main指向原⽣⼩程序⼊⼝
- 第三⽅npm包
- 不使⽤
miniappConfig,使⽤ import Title from 'mini-ali-ui/es/title/index' ⽅式引⼊
- 源码拷⻉到本地
- 拷⻉到 src/miniapp-native 下,使⽤
import Button from '../../miniapp-native/Button/index' (⽽⾮ import Button from '@src/miniapp-native' )
API
⽀持直接在对应环境下使⽤对应api
1 2 3 4 5 6 7 8 9
| import { isMiniApp, isWeChatMiniProgram } from '@uni/env';
function scan () { if (isWeChatMiniProgram) { wx.scanCode() } else if (isMiniApp) { my.scan() } }
|
状态管理
全局状态管理:
- useReducer、useContext etc;–> 不建议
- 提供store
1 2 3 4 5 6
| src ├── models // 全局状态 | ├── counter.ts │ └── user.ts ├── app.tsx └── store.ts
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| export const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));
export default { state: { name: '', id: '' }, reducers: { update (prevState, payload) { return { ...prevState, ...payload, }; }, }, effects: (dispatch) => ({ async updateUserInfo () { await delay(1000); dispatch.user.update({ name: 'taobao', id: '123', }); }, }), };
|
1 2 3 4 5
| import { createStore } from 'rax-app'; import user from './models/user'; const store = createStore({ user }); export default store;
|
1 2 3 4 5 6 7 8 9 10 11
| import store from '@/store'; const HomePage = () => { const [userState, userDispatchers] = store.useModel('user'); return ( <> <span>{userState.id}</span> <span>{userState.name}</span> </> ); }
|
使⽤编译时组件
- 源码⽅式开发
- npm init 运⾏时,npm 安装 jsx2mp-runtime;
- src下创建miniapp-compiled,此⽬录下使⽤编译时编译;
- 在src/miniapp-compiled 下新建 index.jsx ⽂件,将所有编译时组件引⼊后进⾏导出;
- 引⼊时使⽤相对路径,不要⽤解构;
- 限制:
- ⽆法使⽤全局状态管理⽅案,其只拥有从⽗组件获取 props 执⾏渲染,并通过事件与⽗组件通信的能⼒;
- 编译时组件在形态上等同于原⽣⾃定义组件,其依赖的 npm 包建议⽤户配置在 nativePackage 中,必须将 jsx2mp-runtime 加⼊ nativePackage.dependencies 中;
- npm⽅式开发
- Rax组件⽅式开发⼩程序默认采⽤编译时编译组件,可以直接在运⾏时项⽬中使⽤;
- 需要将该组件添加到 package.json 依赖中,还需要将其添加到 build.json 中 nativePackage.dependencies 字段内;
- 在微信⼩程序中使⽤:
- 需要在 Rax 组件的 package.json 中加⼊ “miniprogram”: “.” 兼容 npm 构建机制;
- 在运⾏时项⽬中将 jsx2mp-runtime 添加到 package.json 和 build.json ⾥的 nativePackage.dependencies ;
- 编译完成后,⽤户需要在微信 IDE 中进⾏构建 npm 的操作;
- 参考Rax⼩程序组件开发:
https://rax.js.org/docs/guide/miniapp-com-dev
引⽤⼩程序原⽣⻚⾯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| // 目录 ├── build.json ├── package.json └── src ├── app.js ├──app.json ├── components │ └── Logo │ ├── index.css │ └── index.jsx ├── pages │ ├── About // 小程序原生 │ │ ├── index.axml │ │ ├── index.css │ │ ├── index.js │ │ └── index.json │ └── Home │ ├── index.css │ └── index.jsx └── miniapp-native // 小程序原生 └── List ├── index.axml ├── index.css ├── index.js └── index.json
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // 小程序 src/app.json { "routes": [ { "path": "/", "source": "pages/Home/index" }, { "path": "/about", "source": "pages/About/index", "targets": ["miniapp"] // 声明 } ], "window": { "title": "Rax App" } }
|
Tips:
- 原⽣⻚⾯使⽤到的原⽣⾃定义组件(如上⾯项⽬中的 List 组件)必须放置于 src/miniapp-native ⽂件夹中,否则⽆法使⽤;
- 原⽣⼩程序⻚⾯建议放在 src/miniapp-native ⽂件夹中,⽽不是上⾯ src/pages/About 的做法;
编写原生 app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
import { runApp } from 'rax-app'; runApp({ app: { onLaunch() { this.__age = 20; console.log('on launch'); }, onShow() { console.log('on show'); } } })
console.log('业务逻辑') const originalPage = Page; Page = function(options) { console.log('Page 已被劫持') originalPage(options); }
const nativeAppConfig = { __age: 20, onLaunch() { console.log('on launch'); }, onShow() { console.log('on show'); } }; module.exports = nativeAppConfig;
|
分包加载
- 在 build.json 中设置
1 2 3 4 5 6 7 8
| { "targets": [ "miniapp" ], "miniapp": { "subPackages": true } }
|
- 分包⼊⼝设置
设置分包后,src/app.js 失效,src/app.json routes设置为分包⼊⼝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| { "routes": [ { "source": "pages/Home/app", "miniappMain": true // 主包入口 }, { "source": "pages/About/app" } ], "window": { "title": "Rax app" }, "tabBar": { "textColor": "#999", "selectedColor": "#666", "backgroundColor": "#f8f8f8", "items": [ { "text": "首页", "pagePath": "pages/Home/models/Foo", // 路径修改为具体路径 "icon": "https://gw.alicdn.com/tfs/TB1vGsVqiDsXe8jSZR0XXXK6FXa-200-200.png", "activeIcon": "https://gw.alicdn.com/tfs/TB1EBamvz39YK4jSZPcXXXrUFXa-200-200.png" }, { "text": "关于", "pagePath": "pages/Home/models/Bar", "icon": "https://gw.alicdn.com/tfs/TB1PceUq5pE_u4jSZKbXXbCUVXa-200-200.png", "activeIcon": "https://gw.alicdn.com/tfs/TB1BZTsqmslXu8jSZFuXXXg7FXa-200-200.png" } ], } }
|
- 分包⼊⼝代码
在分包下创建app.js作为⼊⼝,必须引⼊app.json并执⾏ruApp;
1 2 3 4 5 6 7 8 9 10 11 12
| import { runApp } from 'rax-app';
import staticConfig from './app.json'; runApp({ app: { onShow() { console.log('app show...'); }, onHide() { console.log('app hide...'); }, } }, staticConfig);
|
- 分包配置
1 2 3 4 5 6 7 8 9 10
| { "routes": [{ "path": "/about", "source": "pages/index", "miniappPreloadRule": { "network": "wifi", "packages": ["pages/About"] } }] }
|
- 分包间共享内存
公共模块变为单例共享同⼀引⽤
1 2 3 4 5 6 7 8
| { "targets": ["miniapp"], "miniapp": { "subPackages": { "shareMemory": true } } }
|
原⽣⼩程序⼯程配置
在build.json 中的nativeConfig配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { "targets": ["miniapp", "wechat-miniprogram"], "wechat-miniprogram": { "nativeConfig": { "appId": "YOUR_APP_ID", // appId "miniprogramRoot": "build/wechat-miniprogram" }, "subPackages": { "shareMemory": true }, "runtimeDependencies": ["@ali/comp1", "/^raxcomp/"], "nativePackage": { "autoInstall": true, "dependencies": { "mini-ali-ui": "^1.3.4" } } } }
|
性能优化
- 局部场景引⼊编译时组件(双引擎结合)
- 在局部性能要求较⾼的场景下(例如⻓列表),将部分组件(例如⻓列表中的循环项)采⽤编译时⽅案开发;
- 代码逻辑优化
- 多使⽤ memo 等以减少不必要的⼦组件的重复渲染;
- ⾼频组件分级
- 针对 view/image/text 等⾼频使⽤的组件,Rax 会在运⾏时根据其是否绑定 props、events 为其⾃动分级,以映射到对应的模板上,减少⽆⽤的 props、events 绑定,提升交互性能。因此,在编写代码时请注意只为该类组件绑定必需的 props 和 events;
- 模板属性及事件配置
- 运⾏时⽅案中,我们会遍历所有内置组件,并将其输出⾄模板⽂件中,在⼩程序运⾏起来后根据实时的 setData 的数据递归迭代模板以⽣成完整的 DOM 结构。因此组件的 template 会预先绑定上所有的事件和属性,根据运⾏时的数据来进⾏属性传递和事件触发;
- 删除属性及事件:⽀持删除props和events
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { "targets": [ "wechat-miniprogram" ], "wechat-miniprogram": { "template": { "view": { "delete": { "props": ["hover-class", "role"], "events": ["TransitionEnd", "FirstAppear", "TouchMove"] } }, "cover-view": { "delete": { "props": ["scroll-top"] } } } } }
|
- 添加组件属性:只⽀持属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { "targets": [ "miniapp" ], "miniapp": { "template": { "cover-view": { "add": { "props": [ { "name": "test1", "default": "123" }, { "name": "test2", "default": 123 }, { "name": "test3", "default": false } ] } } } } }
|
- 删除⽆⽤template:针对不⽤的原⽣内置组件
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "targets": [ "miniapp" ], "miniapp": { "template": { "delete": [ "canvas", "progress" ] } } }
|
Rax⼩程序API
核⼼API
DOM
- render
在container ⾥通过指定的 Driver, 渲染⼀个 Rax 元素,并返回该根组件的实例;如果提供了可选的回调函数,该回调将在组件被渲染或更新之后被执⾏;
- element:任意需要渲染的 Rax 组件或字符串
- container:任意指定 DOM 渲染容器
- options:
- driver : 指定 Driver,包含:DriverDom、DriverWeex、DriverUniversal
- hydrate: 指定是否开启 hydrate 渲染模式,默认为 false
- 最⼤程度的复⽤容器节点中已有的元素:SEO、SSR
- callback:传⼊回调函数,将在组件被渲染或更新之后被执⾏
1 2 3 4 5 6 7 8 9
| render(element, container, options, [callback]) import { render } from 'rax'; import DriverDom from 'driver-dom';
const HelloMessage = function (props) { return <h1>{props.name}</h1> };
render(<HelloMessage name="world" />, document.body, { driver: DriverDom })
|
- hydrate
最⼤程度的复⽤容器节点中已有的元素
- element:任意需要渲染的 Rax 组件或字符串
- container:为任意指定 DOM 渲染容器
- callback:传⼊回调函数,将在组件被渲染或更新之后被执⾏
1 2 3 4 5 6 7 8
| hydrate(element, container, [callback]) import hydrate from 'rax-hydrate';
function MyComponent(props) { return <h1>Hello world</h1>; } hydrate(<MyComponent />, document.body);
|
- createPortal
提供了⼀种将⼦元素渲染到存在于 DOM 组件层次结构之外的 DOM 节点中。
- child 是任何可渲染的 Rax ⼦元素,例如⼀个元素,字符串或 Fragment;
- container 是⼀个 DOM 元素;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import createPortal from 'rax-create-portal'; const Portal = ({ children }) => { const el = useRef(document.createElement('div')); useEffect(() => { document.body.appendChild(el.current); return () => { el.current.parentElement.removeChild(el.current); }; }, [])
return createPortal(children, el.current); }
function App() { return <div> <Portal> <text>Hello Rax</text> </Portal> </div> }
|
- unmountComponentAtNode
卸载通过 render 函数渲染的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| unmountComponentAtNode(container)
import {createElement, render, useRef, useEffect } from 'rax'; import unmountComponentAtNode from 'rax-unmount-component-at-node'; import View from 'rax-view'; import Text from 'rax-text';
function App() { const ref = useRef(null); useEffect(() => { const result = unmountComponentAtNode(ref.current); }); return <View> <Text ref={ref}>Hello Rax!</Text> </View>; }
|
- findDOMNode
通过ref获取真正的 DOM 元素,以便对 DOM 节点进⾏操作
- component 参数可以是 Rax 元素或者 DOM,均可返回真实 DOM 节点。
1 2 3 4 5 6 7 8 9 10 11 12
| findDOMNode(component) import {createElement, render, useRef, useEffect } from 'rax'; import findDOMNode from 'rax-find-dom-node'; import View from 'rax-view';
function App() { const ref = useRef(null); useEffect(() => { const dom = findDOMNode(ref.current); }); return <View ref={ref} ></View>; }
|
- setNativeProps
通过 setNativeProps 可以直接更改原⽣组件的属性来更新组件状态,避免多次render
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| setNativeProps(node, props?) import { createElement, Component, render, useRef } from 'rax'; import View from 'rax-view'; import Text from 'rax-text'; import setNativeProps from 'rax-set-native-props'; function App() { const textRef = useRef(null); function updateStyle() { setNativeProps(textRef.current, { style: { color: '#dddddd' } }); } return <View> <Text ref={textRef} >setNativeProps</Text> <View onClick={updateStyle}> <Text > </Text> </View> </View> }
|
- getElementById
⾼效查找特定元素的⽅法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| getElementById(id) import { createElement, Component, render } from 'rax'; import View from 'rax-view'; import Text from 'rax-text'; import getElementById from 'rax-get-element-by-id'; function App() { function focus() { getElementById('input').focus(); } return <View> <Input id="input" /> <Text onClick={focus}>input focus</Text> </View> }
|
Element
- createElement
⽤于创建并返回指定类型的 Rax 元素
- type:类型参数为标签名字符串或 Rax 元素;
- props:标签的属性;
- children 从第三个参数开始为元素的⼦节点,有多少个⼦节点就创建多少个;
1 2 3 4 5 6 7
| createElement(type, [props], [...children]) import { createElement } from 'rax'; createElement( 'div', { id: 'foo' }, createElement('p', null, 'hello world') );
|
- cloneElement
以 Rax 元素为模板克隆并返回新的 Rax 元素,将传⼊的 props 与原始元素的 props 浅层合并后返回新元素的 props。新的⼦元素将取代现有的⼦元素,⽽来⾃原始元素的 key 和 ref 将被保留。
- element:Rax 元素;
- props:标签的属性;
- children :从第三个参数开始为元素的⼦节点,有多少个⼦节点就创建多少个;
1 2 3 4 5 6 7 8 9 10 11 12 13
| cloneElement(element, [props], [...children]) import cloneElement from 'rax-clone-element'; import View from 'rax-view'; import Text from 'rax-text'; function Hello({ name }) { const Banner = <Text>Hello Rax!</Text>; return cloneElement(Banner); } function App() { return <View> <Hello></Hello> </View> }
|
- isValidElement
⽤于判断传⼊的对象是否为有效 Rax 元素,返回值为 true 或 false
- object:为 Rax 元素,校验通过返回 true,校验失败返回 false
1 2 3 4 5 6 7 8 9
| isValidElement(object)
import isValidElement from 'rax-is-valid-element'; const Hello = <h1>Hello </h1>; const App = () => <div>{Hello}</div>; console.log('Hello is valid? ', isValidElement(Hello));
console.log('App is valid? ', isValidElement(App));
|
- createFactory
通过⼯⼚⽅法创建 Rax 组件实例,该⽅法就是对 createElement() 的封装;
1 2 3 4 5 6 7 8 9
| createFactory(type) import { createElement } from 'rax'; import createFactory from 'rax-create-factory';
const factory = createFactory('li'); const li1 = factory(null , 'Hello Rax!'); const li2 = factory(null , 'Hello Rax!'); createElement('ul', null, li1, li2);
|
- Children
Children 提供了⽤于处理 props.children 不透明数据结构的实⽤⽅法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import Children from 'rax-children'; function Hello({ children }) { return Children.map(children, (child, i) => { if ( i < 1 ) return; return child; }); } function App() { return <Hello> <Text>Hello</Text> <Text>Rax</Text> </Hello> }
function Hello({ children }) { Children.forEach(children, (child, i) => { if ( i < 1 ) { child.props.children = 'Hello' + child.props.children }; }); return children } function App() { return <Hello> <Text>World</Text> <Text>Rax</Text> </Hello> }
function Hello({ children }) { const count = Children.count(children); return children } function App() { return <Hello> <Text>World</Text> <Text>Rax</Text> </Hello> }
function Hello({ children }) { const only = Children.only(children); return only ? children : null; } function App() { return <Hello> <Text>World</Text> </Hello> }
function Hello({ children }) { const [state, setState] = useState('Rax'); return Children.toArray(children).filter(child => child.props.children === state); } function App() { return <Hello> <Text>Rax</Text> <Text>World</Text> </Hello> }
|
Rax 的核⼼是组件。你可以像嵌套 html 标签那样嵌套 Rax 组件,因为它类似于标记,使得编写 jsx 变得很容易。因为我们使⽤的是 javascript,我们可以改变 props.children。我们可以给他们传递特殊的属性,来决定是否渲染他们并且可以按照我们的意愿去操作他们。 Children 提供了⽤于处理 props.children 不透明数据结构的实⽤⽅法。从本质上来讲,props.children 可以是任何的类型,⽐如数组、函数、对象等等。
Component
- memo
在函数组件,如果你的函数组件在给定相同 props 的情况下渲染相同的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { memo, useState } from 'rax'; import View from 'rax-view'; import Text from 'rax-text'; const useUpdate = () => { const [, setState] = useState(0); return () => setState(num => num + 1); }; const HelloMemo = memo((props) => { console.log('memo-render'); return <Text>{props.children}</Text> }); const HelloNormal = (props) => { console.log('normal-render'); return <Text>{props.children}</Text> } function App() { console.log('render') const update = useUpdate(); const [state, setState] = useState(1); return ( <View> <HelloNormal>Rax</HelloNormal> // 会 rerender <HelloMemo>Hello</HelloMemo> // 不会 rerender <View>{state}</View> <View onClick={() => setState(num => num + 1)}>Update</View> </View> ) }
|
Hooks
⽀持的Hooks包括:
- useState
- useEffect
- useLayoutEffect
- useContext
- useRef
- useCallback
- useMemo
- useReducer
Refs
- createRef
创建一个能够通过 ref 属性附加到 Rax 元素的 ref。当你需要访问节点时,可以通过 ref.current 得到
1 2 3 4 5 6 7 8
| import { createRef, useEffect } from 'rax'; function App() { const inputRef = createRef(); useEffect(() => { inputRef.current.focus(); }, [inputRef.current]); return <input type="text" ref={inputRef} />; }
|
- forwardRef
Ref转发,会创建⼀个 Rax 组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另⼀个组件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { forwardRef } from 'rax'; import View from 'rax-view'; import Text from 'rax-text';
const MyInput = forwardRef((props, ref) => ( <input type="text" ref={ref}></input> ));
function App() { const ref = createRef();
const focus = () => { ref.current.focus(); }; return ( <View> <MyInput ref={ref}></MyInput> <View onClick={focus}>Click</View> </View> ) }
|
Fragment
用于减少不必要嵌套的组件
1 2 3 4 5 6 7 8 9
| <Fragment> <header>A heading</header> <footer>A footer</footer> </Fragment>
<> <header>A heading</header> <footer>A footer</footer> </>
|
Context
创建⼀个 Context 对象。当 Rax 渲染⼀个订阅了这个 Context 对象的组件,这个组件会从组件树中离⾃身最近的那个匹配的 Provider 中读取到当前的 context 值。
不建议,如果有需要可以使⽤store
1 2
| import { createContext } from 'rax'; const MyContext = createContext(defaultValue);
|
PropTypes
要在组件的 props 上进行类型检查,只需配置特定的 propTypes 属性 -> 建议使用 TS
1 2 3 4 5 6 7 8 9 10 11 12 13
| import PropTypes from 'prop-types'; MyComponent.propTypes = { optionalArray: PropTypes.array, optionalBool: PropTypes.bool, optionalFunc: PropTypes.func, optionalNumber: PropTypes.number, optionalObject: PropTypes.object, optionalString: PropTypes.string, optionalSymbol: PropTypes.symbol, optionalNode: PropTypes.node }
|
version
获取当前Rax core版本
1 2 3
| import { version } from 'rax'; console.log('version: ', version);
|
其他扩展Hooks
| 名称 |
描述 |
| useMountedState |
获取当前mounted状态 |
| useMounted |
组件mounted回调 |
| useUnmount |
组件unmounted回调 |
| useInterval |
setInterval基础上,在组件mount前clearInterval避免错误发送 |
| useOnceEffect |
effect执⾏⼀次 |
| useTimeout |
似useInterval,在组件mount前clearTimeout避免错误发送 |
| useAsyncEffect |
⽤于异步的effect |
| useOnceAsyncEffect |
异步effect执⾏⼀次 |
| usePromise |
适⽤于promise场景 |
| useFetch |
适⽤于fetch请求场景 |
| useImport |
动态引⼊组件 |
| useCountDown |
返回倒计时时分秒等信息 |
Uni API
基于Rax 跨端开发的能⼒,推出了 Uni API,抹平了Web、Weex、多端⼩程序的差异;
基础
| 名称 |
描述 |
| env |
判断和获取运⾏时环境(如isWeb、isWeex、isMiniApp etc) |
| canIUse |
判断 API 是否可⽤ |
| request |
⽤于发起⽹络请求 注意:此 API 不⽀持 promise 调⽤ |
| unitTool |
⼯具库,px2rpx, rpx2px |
应用
| 名称 |
描述 |
| getApp |
取全局唯⼀应⽤实例 |
| getCurrentPages |
获取当前⻚⾯栈。数组中第⼀个元素为⾸⻚,最后⼀个元素为当前⻚⾯ |
| getLaunchOptionsSync |
获取⼩程序启动时的参数 |
| unitTool |
⼯具库,px2rpx, rpx2px |
| onError |
错误事件的监听 |
| onUnhandledRejection |
错误事件的监听的promise拒绝事件 |
| offError |
取消错误事件的监听 |
| onUnhandledRejection |
取消错误事件的监听的promise拒绝事件 |
设备
| 名称 |
描述 |
| scan |
获取全局调⽤扫⼀扫功能的 API |
| getClipboard |
获取当前⻚⾯栈获取系统剪贴板的内容 |
| setClipboard |
设置系统剪贴板的内容 |
| systemInfo |
获取系统信息 |
| makePhoneCall |
拨打电话 |
文件
| 名称 |
描述 |
| download |
下载资源到本地 |
| getInfo |
下载资源信息 |
| getSavedInfo |
获取保存的⽂件 |
| openDocument |
在新⻚⾯打开预览 |
| removedSaved |
移除保存的⽂件 |
| upload |
上传⽂件到服务器 |
界面
| 名称 |
描述 |
| alert |
警告框 |
| confirm |
模态对话框 |
| getScrollOffset |
获取元素移动位置信息 |
| getBoundingClientRect |
获取元素getBoundingClientRect |
| getMenuButtonBoundingClientRect |
解获取菜单按钮(右上⻆胶囊按钮)的布局位置信息 |
| showLoading/hideLoading |
显示/关闭 loading 提示框 |
| showToast/hideToast |
显示/关闭 Toast 提示框 |
| showTabBar/hideTabBar |
显示/关闭 tabBar |
| actionSheet |
显示操作菜单 |
| intersectionObserver |
⽤于推断某些节点是否可以被⽤户看⻅、有多⼤⽐例可以被⽤户看⻅ |
| onPullDownRefresh |
开启下拉刷新 |
| startPullDownRefresh/stopPullDo wnRefresh |
开始/关闭下拉刷新 |
| createTransition |
创建⼀个过渡动画 |
| createAnimation |
创建⼀个过渡动画实例 |
| setNavigationBarColor |
设置⻚⾯导航条颜⾊ |
| setNavigationBarTitle |
设置⻚⾯导航条⽂案 |
| pageScrollTo |
滚动到制定位置 |
多媒体
| 名称 |
描述 |
| chooseImage |
本地选择相册或拍照 |
| chooseMedia |
拍摄或从⼿机相册中选择图⽚或视频 |
| chooseVideo |
拍摄视频或从⼿机相册中选视频 |
| compressImage |
压缩图⽚ |
| createAudioContext/create 、VideoContext |
创建⾳视频 |
| getImageInfo |
获取照⽚信息 |
| previewImage |
全屏预览图⽚ |
| saveImage |
保存图⽚到本地相册 |
| RecorderManager |
获取录⾳管理器 |
路由
| 名称 |
描述 |
| navigate |
go、replace、back、push、relaunch、switchTab |
缓存
| 名称 |
描述 |
| getStorage |
从本地缓存中异步获取指定 key 的内容 |
| getStorageSync |
从本地缓存中同步获取指定 key 的内容 |
| removeStorage |
从本地缓存中异步移除指定 key |
| removeStorageSync |
从本地缓存中同步移除指定 key |
| setStorage |
将数据异步存储在本地缓存中指定的 key 中。会覆盖掉原来该 key 对应的内容 |
| setStorageSync |
将数据同步存储在本地缓存中指定的 key 中。会覆盖掉原来该 key 对应的内容 |
https://rax.js.org/docs/api/about
Rax⼩程序组件
基础元件
即通⽤的universal组件,⽀持Web、各种⼩程序;
| 属性名 |
类型 |
描述 |
| style |
object |
为元素设置内联样式 |
| className |
string |
类选择器,允许以⼀种独⽴于⽂档元素的⽅式来指定样式 |
| 事件名 |
类型 |
描述 |
| onClick |
function |
当组件被点击时触发的事件 |
⽀持的基础元件包括:
| 基础元件 |
描述 |
| Text |
⽤于显示⽂本,在 web 中实际上是⼀个 span 标签⽽⾮ p 标签 |
| View |
最基础的组件,它⽀持 Flexbox、touch handling 等功能,并且可以任意嵌套,像 web 中的 div 。 ⽀持任意⾃定义属性的透传 |
| TextInput |
TextInput 是唤起⽤户输⼊的基础组件;当定义 multiline 输⼊多⾏⽂字时其功能相当于 textarea; |
| Link |
Link 是基础的链接组件,同 a 标签。它带有默认样式; ⼩程序场景使⽤ navigator 标签 |
| Icon |
图标组件 |
| Image |
展示图⽚ |
| Video |
视频播放组件 |
| ScrollView |
包装了滚动操作的组件。需要⼀个确定的⾼度来保证 ScrollView 的正常展现 |
| Waterfall |
实现瀑布流容器 |
| Portal |
提供了“传送”能⼒,可以将任意 RaxNode 渲染⾄根节点(body),⻅example |
| Embed |
内嵌内容容器,在 weex 下为 <web> 实现,在 web 下为<iframe> <embed> 实现,⼩程序中实现为<webview> |
| Countdown |
倒计时组件 |
| Swiper |
轮播组件 |
| Modal |
弹出遮罩层的能⼒,为 Alert, Confirm 等对话框组件提供底层能⼒ |
基础组件
使⽤Fusion Mobile作为移动端的组件库
使⽤⽅式
1 2
| npm install @alifd/meet -S npm i build-plugin-fusion-mobile -D
|
1 2 3 4 5 6 7 8 9 10
| { "targets": [ "web", "wechat-miniprogram" ], "plugins": [ "@ali/build-plugin-rax-app-def", "build-plugin-fusion-mobile" ] }
|
1 2 3 4 5 6 7 8 9 10
| import { createElement } from 'rax'; import { Button } from '@alifd/meet'; import '@alifd/meet/es/core/index.css'; export default function Home() { return ( <> <Button type="primary">Hello World</Button> </> ); }
|
https://rax.js.org/docs/components/meet-about
补充知识点