沉寂了两个月,我又回来了。
跟你们猜的一样,我已经到淘系实习了一段时间了,从上一篇文章之后就放了更多的心思在工作上。上篇文章发出去之后,我去腾讯实习了一段时间,等待阿里实习生入职流程开启。
收到淘系的实习生 offer 后,我买了人生中的第一张机票,第一次坐上了飞机,来到了一个陌生的城市——杭州。干净的街道、宽敞的沥青马路,吸引了我这个来自小城市的年轻人。
博客很久没更新了,不管是个人的网站还是掘金,都很少有更新了,偶尔上掘金看看一些好文,总想更新一下,但又没找到好的题材。现在实习了一段时间后,上手了淘系的开源跨端框架——Rax,新奇又好玩,也总结了一些很基础的开发技巧,补充一下官方文档的缺漏(官方文档对于新手来讲确实不太友好)。
基础部分演示项目 Git
仓库:Rax-TODO
进阶技巧演示项目 Git
仓库:Software-Engineering(同时也是我的课程设计,欢迎大伙点个 Star
)
前面的部分是针对只有一点 React
基础的同学的,高端玩家请点击:进阶技巧
这部分官网文档比较详细了,先看一下官网文档:快速开始
官网文档提供的方案还挺多的,可能会选择困难,我们就从最基础的 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)
? 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
的地址,查看我们的项目了。
随便选择一款自己喜欢的 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 和 基础组件-Text
按照 耦合度
,我们应该将组件放在 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
// 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
后会发现页面变成了这样:
我们在最开始创建项目的时候,并没有选择多平台,但是 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 Preview
后,你会发现是一堆 JavaScript
代码,那怎么预览?
有一个稍微麻烦一点的方法,就是先确定你本机在内网的 IP
地址,将 URL
中的 localhost
改成内网 IP
,然后将 URL
转换为二维码(Chrome
有很多插件可以实现)。
在手机上下载 Weex Playground
然后扫描二维码,就能在手机上预览到 APP 的效果。
Weex Playground 下载地址
在前面的项目里,我们没有引入 TypeScript
,那如果想引入应该怎么办?
很简单,在官网的文档中也有讲解:项目开发- TypeScript 支持
只需要先安装 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
官网处理静态文件。
一切配置完成后,就可以在一个需要做测试的组件下创建 __test__
文件夹并将测试脚本放在文件夹下了,或者你也可以直接使用 xxx.test.jsx
为文件命名,这些文件都会被 Jest
识别为测试脚本。
除了 Jest
,Rax 团队还为 Rax 提供了 Enzyme
适配器,示例项目:raxjs/enzyme-adapter-rax,提供了 Enzyme
的使用示例。
得益于淘系强大的技术实力,Rax 的相关生态相对比较完善。同时,性能也非常强大,这里我就不过多介绍了。
Rax 在小程序上的性能甚至优于市面上其他小程序框架:Rax ——完美融合编译时与运行时的双引擎小程序框架。
其他的一些介绍 Rax 的文章可以移步官网的博客。
文章的最后,推荐一下自己的公众号:Hello FE,是我和跟我一起实习的小伙伴们一起运营的,会定期分享一些干货。
同时,关注公众号还有前端书籍大全一份赠送,欢迎大家关注,也欢迎大家进群交流: