❝沉寂了两个月,我又回来了。
❞
跟你们猜的一样,我已经到淘系实习了一段时间了,从上一篇文章之后就放了更多的心思在工作上。上篇文章发出去之后,我去腾讯实习了一段时间,等待阿里实习生入职流程开启。
收到淘系的实习生 offer 后,我买了人生中的第一张机票,第一次坐上了飞机,来到了一个陌生的城市——杭州。干净的街道、宽敞的沥青马路,吸引了我这个来自小城市的年轻人。
博客很久没更新了,不管是个人的网站还是掘金,都很少有更新了,偶尔上掘金看看一些好文,总想更新一下,但又没找到好的题材。现在实习了一段时间后,上手了淘系的开源跨端框架——Rax,新奇又好玩,也总结了一些「很基础」的开发技巧,补充一下官方文档的缺漏(官方文档对于新手来讲确实不太友好)。
基础部分演示项目 Git
仓库:Rax-TODO[1]
进阶技巧演示项目 Git
仓库:Software-Engineering[2](同时也是我的课程设计,欢迎大伙点个 Star
)
前面的部分是针对只有一点 React
基础的同学的,高端玩家请点击:进阶技巧[3]
这部分官网文档比较详细了,先看一下官网文档:快速开始[4]
官网文档提供的方案还挺多的,可能会选择困难,我们就从最基础的 TODO 开始,使用 Rax 搭建一个 Web 项目:
npm init rax todo-list
输入上面的命令之后,会使用 npx
安装 rax-cli
脚手架工具,安装完成后会弹出这样的界面:
added 106 packages from 53 contributors in 10.5s
? What's your project type? (Use arrow keys)
❯ App (Build universal application)
Component (Build universal component)
API (Build universal API library)
Plugin (Build plugin for miniapp)
使用 上下键
移动箭头,后面的操作同理。
这里我们就直接使用默认的选项,创建一个 APP,按下回车。
? Choose targets your project want to run? (Press <space> to select, <a> to togg
le all, <i> to invert selection)
❯◉ Web
◯ Weex
◯ Kraken (Flutter)
◯ Alibaba MiniApp
◯ WeChat MiniProgram
使用 空格键
选择,按下 字母a
可以全选,这里我们就先选择 Web,后续如果有编译为 Weex
和 小程序
的需求可以在项目目录的 build.json
中添加。
编译为 Flutter
应用的功能目前还不稳定,且坑比较多,建议动手能力强的玩家尝试,小白就先绕道吧。
? What's your application type? (Only valid in target: Web/Weex/Kraken) (Use arr
ow keys)
❯ Single-page application (SPA)
Multi-page application (MPA)
Create lite application (The simplest project setup)
选择 APP 的类型,是 SPA
还是 MPA
,这里的选项只在 Web/Weex/Kraken(Flutter)
应用中有效。
我们选择默认的选项 SPA
,不明白 SPA
和 MPA
的区别的同学,可以移步文章:认识单页应用(SPA)与多页应用(MPA)[5]
? What's author's name? (rax): 炽翎
? What type of language do you want to use? (Use arrow keys)
❯ JavaScript
TypeScript
这里我们就先选择 JavaScript
,后续如果有引入 TypeScript
的需求,可以手动引入。
? Do you want to enable these features? (Press <space> to select, <a> to toggle
all, <i> to invert selection)
❯◯ Server-side rendering (SSR) (Only valid in target: Web)
◯ Aliyun Function Compute (FaaS) (Only valid in target: Web)
◯ Compatibility with React
这里我们就什么都不选。
当然,如果有需要我们可以选择 Compatibility with React
配置与 React 的兼容,也可以为我们的应用开启 服务端渲染(SSR)
。如果有需求开启 Serverless
,也可以选择开启 功能即服务(FaaS)
。SSR
和 FaaS
都只能在 Web
应用中有效。
不懂 SSR
和 Serverless
的同学请自行百度(Google)哈。
? Do you want to install dependences automatically after initialization? (Y/n)
这里就直接回车,这个选项的意思是询问是否在初始化完成后自动安装依赖。默认是 Yes
,就不用我们手动进入项目文件夹执行 npm install
了。
To run your app:
cd todo-list
npm start
当终端出现这一段文字的时候,就说明依赖安装完了,我们可以进入到项目的文件夹下,执行 npm start
跑起我们的项目。
在执行 npm start
后,终端会显示:
Rax development server has been started:
[Web] Development server at:
http://localhost:3333/
这时候我们就可以在浏览器输入 Dev Server
的地址,查看我们的项目了。
Home
随便选择一款自己喜欢的 IDE,进入项目的文件夹,我们会看到这样的一个目录结构:
.
├── README.md # 项目说明
├── build.json # 项目构建配置
├── package.json
└── src # 源码目录
├── app.js # 应用入口文件
├── app.json # 应用配置,包括路由配置,小程序 window 配置等
├── public # (可选)静态资源目录,会拷贝内容至 build 目录
├── components # 应用的公共组件
│ └── Logo # 组件
│ ├── index.css # Logo 组件的样式文件
│ └── index.jsx # Logo 组件 JSX 源码
├── document # 页面的 HTML 模板
│ └── index.jsx
└── pages # 页面
└── Home # home 页面
└── index.jsx
开发之前,我们需要了解一些 Rax 下的一些小规矩:
rpx
:默认以 750rpx
为屏幕宽度,即 1rpx = 1/750 * 屏幕宽度
。样式简写
:在 Rax 中,由于目前兼容性的问题,不支持 部分
样式简写,例如:在写 border
样式时,应该将各个部分分开:border-width
、border-style
、border-color
。在遇到属性简写在非 Web 平台不生效的问题时,尝试将属性分开或许就能解决问题。我们要做的第一件事是删除初试化后默认的 Home
页面中的 Logo
组件,在 src/components
文件夹下,整个删除。
删除之后,src/pages/Home/index.jsx
肯定会报错,我们先稍作修改,改成 Hello World!
。
// index.jsx
import { createElement } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import './index.css';
export default function Home() {
return (
<View className="home">
<Text>Hello World!</Text>
</View>
);
}
这个时候会发现页面变成了 Hello World!
,后面应该不用讲解太多,就是使用 React
应用开发的方式,开始愉快的 coding
过程。
可能你会发现,为什么在这里我们只能用 Rax 提供的 View
和 Text
组件?
因为为了使跨端显示效果一致,Rax 为开发者抹平了不同平台之间样式显示不一致的问题。
当然,如果你只是想用 Rax 做一个 Web 应用,那你可以使用 HTML
标签(汗)。
View
组件和 Text
组件的用法可以移步文档:基础组件-View[6] 和 基础组件-Text[7]
按照 耦合度
,我们应该将组件放在 src/pages/Home
文件夹下。
在这个文件夹下,我们创建一个这样的目录:
src/pages/Home
└── components # 组件文件夹
└── ListItem # ListItem 组件
├── index.css # CSS
└── index.jsx # JSX
首先我们应该构思一下这个 ListItem
应该暴露给父组件哪些接口:
id
onClick
事件明确了组件应该暴露的接口之后,就可以开始写代码了:
// index.jsx
import { createElement } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import './index.css';
const ListItem = (props) => {
// 解构 id 完成状态 内容 onClick 事件
const { id, done, content, onClick } = props;
// 完成项文字样式
const style = {
fontSize: '64rpx',
lineHeight: '96rpx',
textDecoration: done && 'line-through'
};
return (
<View className="list-item" onClick={() => onClick(id)}>
<View className="list-dot"></View>
<Text style={style}>{content}</Text>
</View>
);
};
export default ListItem;
/* index.css */
.list-item {
flex-direction: row;
justify-content: flex-start;
align-items: center;
width: 100%;
height: 100rpx;
}
.list-dot {
width: 20rpx;
height: 20rpx;
margin-right: 20rpx;
border-radius: 10rpx;
background-color: #333;
}
还是一样,先创建文件:
src/pages/Home
└── components # 组件文件夹
├── List # List 组件
│ ├── index.css # CSS
│ └── index.jsx # JSX
└── ListItem # ListItem 组件
├── index.css # CSS
└── index.jsx # JSX
创建一个 List
组件作为 ListItem
的容器管理所有的 Item
,实现 添加/删除
的功能。
既然要做 添加
功能,那输入框肯定是必不可少的,在我们创建的项目里,默认是没有输入框组件的依赖的,所以我们要先安装 Rax 提供的输入框组件:
npm install rax-textinput --save
组件的用法可以转战文档:基础组件-TextInput[8]
// index.jsx
import { createElement, useState } from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import TextInput from 'rax-textinput';
import ListItem from '../ListItem';
import './index.css';
const List = () => {
// 初始化 itemId 每次添加新列表项就 +1
const [itemId, setItemId] = useState(0);
// 初始化列表
const [list, setList] = useState([]);
// 初始化 TextInput 内容
const [inputValue, setInputValue] = useState('');
// 输入框输入事件
const handleUserInput = (e) => {
setInputValue(e.target.value);
};
// 添加按钮点击事件
const handleAddButtonClick = () => {
// 构造列表项数据结构
const item = {
id: itemId,
content: inputValue,
done: false
};
// immutable 思想 生成新的引用
const newList = [...list, item];
setList(newList);
// 清空输入框
setInputValue('');
// itemId ++
setItemId(itemId + 1);
};
// 列表项点击事件
const handleItemClick = (id) => {
// 遍历列表 当事件未完成时标记为已完成 当事件已完成时删除
const newList = list.filter((item) => {
if (item.id === id) {
if (item.done) {
return false;
} else {
item.done = true;
}
}
return true;
});
setList(newList);
};
return (
<View className="list">
<View className="list-input-wrapper">
<TextInput
className="list-input"
value={inputValue}
onInput={handleUserInput}
/>
<View className="list-add-button" onClick={handleAddButtonClick}>
<Text>添加</Text>
</View>
</View>
<View className="list-item-wrapper">
{list.map((item) => (
<ListItem
key={item.id}
id={item.id}
content={item.content}
done={item.done}
onClick={handleItemClick}
/>
))}
</View>
</View>
);
};
export default List;
.list {
align-items: center;
}
.list-input-wrapper {
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.list-add-button {
justify-content: center;
align-items: center;
width: 150rpx;
height: 100rpx;
margin-left: 20rpx;
border-radius: 10px;
background-color: #dcdcdc;
}
.list-input {
width: 100%;
height: 100rpx;
line-height: 96rpx;
font-size: 64rpx;
border-width: 1px;
border-color: #dcdcdc;
}
.list-item-wrapper {
width: 100%;
}
至此,组件的基本功能就完成了。
现在我们要将组件引入首页 Home
中:
import { createElement } from 'rax';
import View from 'rax-view';
import List from './components/List';
import './index.css';
export default function Home() {
return (
<View className="home">
<List />
</View>
);
}
现在就能看到一个能完成基本功能的 TODO 应用了。
我猜,有些高玩看到我前面写的部分,肯定会说:
❝啊~ 你这个这么简单,不是有手就行嘛~
❞
有一说一,确实有手就行(误)。
前面的部分只是写给不爱读官方文档或者只有一点 React
基础的同学的,从这个部分开始,会开始引入一些高级的内容。
在前面创建的项目中,我们会看到项目的文件夹下有一个 app.json
文件,打开它你会看到:
{
"routes": [
{
"path": "/",
"source": "pages/Home/index"
}
],
"window": {
"title": "Rax App"
}
}
这个文件就是用来控制项目路由,如果后续的开发增加了更多的页面,就需要在 routes
下添加对象。path
就是你新加入的页面的访问路径(URL
),source
就是你新添加的页面所在的文件目录。
在项目文件夹下还有一个 build.json
文件,里面的内容是这样的:
{
"plugins": [
[
"build-plugin-rax-app",
{
"targets": ["web"]
}
]
]
}
因为我们之前创建项目的时候,选择的是单页应用(SPA),我们想改成多页应用怎么办?很简单:
先安装多页应用依赖:
npm install build-plugin-rax-multi-pages --save-dev
然后在 targets
所在的对象下,增加一个键 "type"
,值为 "mpa"
,像这样:
{
"plugins": [
[
"build-plugin-rax-app",
{
"targets": ["web"],
"type": "mpa"
}
]
]
}
重启 Dev Server
后会发现页面变成了这样:
MPA
我们在最开始创建项目的时候,并没有选择多平台,但是 Rax 作为一个跨端开发框架,不添加跨端支持怎么行,这里会给一个教程教大家添加跨端支持(当然一开始就选择好更方便)。
还是 build.json
文件,有一个 targets
数组,想要添加跨端支持,就在数组中添加对应的内容即可:
{
"plugins": [
[
"build-plugin-rax-app",
{
"targets": ["web", "weex", "miniapp", "wechat-miniprogram"],
"type": "mpa"
}
]
]
}
weex
对应 Weex
平台,miniapp
和 wechat-miniprogram
对应 阿里小程序
和 微信小程序
。
我们在 build.json
中使用了上面的配置后,页面会变成这个样子:
Weex
点开 Weex Preview
后,你会发现是一堆 JavaScript
代码,那怎么预览?
有一个稍微麻烦一点的方法,就是先确定你本机在内网的 IP
地址,将 URL
中的 localhost
改成内网 IP
,然后将 URL
转换为二维码(Chrome
有很多插件可以实现)。
在手机上下载 Weex Playground
然后扫描二维码,就能在手机上预览到 APP 的效果。
Weex Playground 下载地址[9]
在前面的项目里,我们没有引入 TypeScript
,那如果想引入应该怎么办?
很简单,在官网的文档中也有讲解:项目开发- TypeScript 支持[10]
只需要先安装 rax-types
和 typescript
:
npm install rax-types typescript --save-dev
在项目文件夹下创建 tsconfig.json
文件,使用以下配置:
{
"compilerOptions": {
"module": "esNext",
"target": "es2015",
"outDir": "build",
"jsx": "preserve",
"jsxFactory": "createElement",
"moduleResolution": "node",
"sourceMap": true,
"alwaysStrict": true,
"baseUrl": ".",
"paths": {
"rax": ["node_modules/rax-types"]
}
},
"include": ["./src/**/*"]
}
即可添加 TypeScript
支持。
这是一个比较大的坑,以至于让我摸索了一个上午才解决。
Rax 官方文档中并没有自动化测试相关配置的教程,以至于我只能直接在钉钉找负责 Rax 维护的师兄求助,历经千辛万苦终于将测试用例跑通。
首先,我们需要安装几个依赖,数量有点多,建议分开安装:
npm install jest --save-dev
npm install @types/jest --save-dev
npm install @babel/preset-env --save-dev
npm install babel-jest --save-dev
npm install rax-test-renderer --save-dev
npm install @babel/plugin-proposal-class-properties --save-dev
npm install @babel/plugin-proposal-decorators --save-dev
npm install @babel/plugin-proposal-export-default-from --save-dev
npm install @babel/preset-flow --save-dev
npm install @babel/preset-react --save-dev
npm install babel-plugin-transform-jsx-stylesheet --save-dev
依赖安装好之后,在项目的目录下创建 .babelrc.js
对 Babel
进行配置:
module.exports = function (api) {
// Cache the returned value forever and don't call this function again.
if (api) api.cache(true);
return {
presets: [
'@babel/preset-flow',
[
'@babel/preset-env',
{
loose: true
}
],
[
'@babel/preset-react',
{
pragma: 'createElement'
}
]
],
plugins: [
'@babel/plugin-proposal-export-default-from',
['@babel/plugin-proposal-class-properties', { loose: false }],
'babel-plugin-transform-jsx-stylesheet',
['@babel/plugin-proposal-decorators', { legacy: true }]
],
ignore: ['build', 'coverage', 'node_modules']
};
};
配置好之后,Jest
还是无法跑通的,要在 package.json
中添加启动脚本:
{
"scripts": {
"build": "build-scripts build",
"start": "build-scripts start",
"lint": "eslint --ext .js --ext .jsx ./",
// 添加 jest 启动脚本
"test": "jest"
}
}
同时,还要对 Jest
进行配置:
{
"jest": {
"collectCoverage": true,
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
}
}
}
配置添加完成之后,在项目的目录下创建一个文件夹:
.
└── __mocks__
├── fileMock.js # 静态资源处理
└── styleMock.js # 样式处理
分别在两个文件中加入添加代码:
// fileMock.js
module.exports = 'test-file-stub';
// styleMock.js
module.exports = {};
配置这个部分的目的是让 Jest
处理样式和静态资源时到 mock 的文件夹下找。因为在测试的时候,测试脚本并不会真的去访问引入的静态资源,遇到需要引用静态资源的地方就引导到 __mocks__
文件夹下查找,就不会因为找不到资源而报错。配置来源于 Jest
官网处理静态文件[11]。
一切配置完成后,就可以在一个需要做测试的组件下创建 __test__
文件夹并将测试脚本放在文件夹下了,或者你也可以直接使用 xxx.test.jsx
为文件命名,这些文件都会被 Jest
识别为测试脚本。
除了 Jest
,Rax 团队还为 Rax 提供了 Enzyme
适配器,示例项目:raxjs/enzyme-adapter-rax[12],提供了 Enzyme
的使用示例。
得益于淘系强大的技术实力,Rax 的相关生态相对比较完善。同时,性能也非常强大,这里我就不过多介绍了。
Rax 在小程序上的性能甚至优于市面上其他小程序框架:Rax ——完美融合编译时与运行时的双引擎小程序框架[13]。
其他的一些介绍 Rax 的文章可以移步官网的博客[14]。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8