Releases: QianmiOpen/plume2
改进relaxProps和@Action
@action支持默认值,默认就是函数名称
class HelloActor extends Actor {
//action name is update
@Action()
update(state) {}
}
在relax的props的彻底的数组实现,新的默认规则,
static relaxProps = [
'hello', //默认名字一样 hello
['goodsList',0, 'goods'], //默认使用最后的一个名字,goods
helloQL, //取QL('name'), 比如:helloQL
helloPQL, //取PQL('name'), 比如:helloPQL
]
感谢@dr2009杨兴洲同学的pr。
release 1.2.1
RL = reactive language
有时候我们的代码的flow不能单纯的围绕着页面触发的事件来设计,
因为可能出现多个页面的事件对于的可能只是一件事情和后端的数据同步
为了更简单的实现这种反应式的设计,我们设计实现了RL,和QL的语法类似
const helloRL = RL('helloRL', [
'hello', //依赖的数据路径或者QL可以是多个,和QL类似
(hello) => {
//当数据发生变化的时候会触发
webapi.syncData();
}
])
class AppStore extends Store {
bindRx() {
return {helloRL}
}
}
release plume2@1.1.1
为了快速实践plume2的最佳实践,我们提供一站式脚手架
hufeng@zen ~> plume2
Usage: plume2 [options] [command]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
new [name] create a new plume2 app
help [cmd] display help for [cmd]
hufeng@zen /p/t/test> plume2 new hello
😁 create hello successfully.
hufeng@zen /p/t/test> tree -L 3
.
└── hello
├── actor
│ └── index.ts
├── command.ts
├── component
│ └── index.tsx
├── index.tsx
├── ql.ts
├── store.ts
├── typings.ts
├── view-action
│ └── index.ts
└── webapi.ts
4 directories, 9 files
为了快速实现代码,我们设计了plume2-vscode的插件快速生成代码片段
祝码的开心。
release 1.1.0
Relax包住的组件, 父组件无法调用实例方法
relaxProps支持数组字符串格式
plume2直接导出immutable,不需要再从immutable引入
优化开发体验:Relax判断如果context中没有plume2_store提示没有添加StoreProvider绑定
add viewAction
当页面比较复杂的时候,store中就会有大量的事件回调方法,而且注入到页面需要记住函数的名称
这样就会造成很多的重复代码和不方便,而且会丧失一些类型的定义,所以我们抽取一个ViewAction
//ViewAction
import { ViewAction } from 'plume2';
import { Command } from '../command';
export class CounterViewAction extends ViewAction {
increment = () => {
this.store.dispatch(Command.INCREMENT);
};
decrement = () => {
this.store.dispatch(Command.DECREMENT);
};
}
//Store
import { Store } from 'plume2';
import CounterActor from './actor/counter-actor';
import * as ViewAction from './view-action';
export default class AppStore extends Store {
bindActor() {
return [new CounterActor()];
}
bindViewAction() {
return ViewAction;
}
}
//relax component
import { Relax } from 'plume2';
import React from 'react';
import { TCounterViewAction } from '../typings';
@Relax
export default class Counter extends React.Component {
props: {
relaxProps?: {
count: number;
viewAction: TCounterViewAction;
};
};
static relaxProps = {
count: 'count',
viewAction: 'viewAction'
};
render() {
const { count, viewAction } = this.props.relaxProps;
return (
<div>
<a href="javascript:;" onClick={viewAction.CounterViewAction.decrement}>
decrement
</a>
<span>{count}</span>
<a href="javascript:;" onClick={viewAction.CounterViewAction.increment}>
increment
</a>
</div>
);
}
}
理论联系实践,在实践中不断的完善架构
plume2是我们的一个新的起点,是我们走向typescript的起点
plume2完全站在typescript静态和编译角度去思考框架的特性和实现
我们希望plume2足够轻量,简单,一致,同时给出优雅的代码检查
错误提示,全链路的log跟踪,就想我们的开发能够轻松一点。
在我们实践过程中,也会一些不够细致地方,我们需要不断的去改进。
在怎么去思考改进都不为过,
划重点 开发体验同用户体验一样重要
improvements
- 干掉DQL,DQL有些鸡肋,这就是理想和现实的差别,DQL实现过程中需要动态递归的替换模板变量也是比较受罪,更重要的事,DQL的动态数据的来源只能是React的Component的props,这就带来了一些不够灵活,比较受限。我们设计DQL或者QL本意是是什么,是获取数据声明式(Declarative)以及数据本身的反应式(reactive). 为了解决这个问题,我们设计了更简单的PQL(partial Query Lang)
import {PQL, Relax} from 'plume2'
const helloPQL = PQL(index => QL([
['users', index, 'name'],
(name)=>name
]);
@Relax
class HelloContainer extends React.Component {
static relaxProps = {
hello: helloPQL
}
render() {
const value = hello(1);
return <div>{value}</div>
}
}
简单清晰实现,更灵活的参数入口。目前不支持PQL嵌套PQL。
- 更舒服的开发体验
有时候我们为了快速的在浏览器的控制台如(chrome console)去快速测试我们的一些store的方法,我们会写
class AppStore extends Store {
constructor(props: IOptions) {
if (__DEV__) {
window['_store'] = this;
}
}
}
这样可以在控制台直接调用_store去快速测试。 但是经常这样写,每个页面都这样写,就有点小烦躁,无缘无故去写个构造方法,也挺无趣。关键是如@angrycans 豹哥之前反馈,这样在一些SPA或者react-native的多页面中_store会被重复覆盖,也挺着急。那现在我们就从框架层面去解决这个问题。当开启应用的debug特性的时候,框架自动绑定。来简化这个流程。如:
//开启debug-mode
@StoreProvder(AppStore, {debug: true })
class HelloApp extends React.Component {
render() {
return <div/>
}
}
plume2会自动在window上面绑定_plume2App, 各个key就是storeprovider下的组件名称。
这样小伙伴尽情玩耍就可以了。
- 更好的事件处理模块
目前我们的UI交互的事件的handler都在store中,因为我们希望UI是less-logic这样才好通用我们业务层。
之前都是通过relax和relaxProps去injected我们store的方法给UI的交互逻辑,如:
const noop = () => {};
@Relax
class HelloApp extends React.Component {
props: {
relaxProps?: {
OnInit: Function;
onReady: Function;
onShow: Function;
}
}
static relaxProps = {
onInit: noop,
onReady: noop,
onShow: noop
}
}
这样的有点是简单,通过relax注入就完事了,就一个规则只要方法的名字和store的method名字相同就ok。
但是实践下来,同学们发现写一遍注入,再写一遍typescript类型定义,心里真是万马奔腾的感觉,太重太累。
更有甚者,我们可能某个很叶子节点的组件,仅仅是想回调一个事件,都要通过relax来注入,如果有列表数据的场景,设计的不当如果每个item都是relax,页面200条数据,那就是200relax组件啊。relax本质上是subscribe container component, 它会实时监听store的变化,这200个。。哗啦啦的性能下降啊。
So我们需要rethink。
在react里面,我们怎么定义UI
UI = f(state) 其实这个并不完整,这个仅仅是定义了UI的展现部分,UI还有交互,交互在函数式观点事件就是副作用。
因此更完整的定义应该是UI = f(state, action),继续往下思考,什么是state?站在flux的角度去看,
state = store(initState, action),😜,是不是很熟悉,都有Action,这是不是有什么关联?其实就是一个是出口,一个是入口。
UI(state, action)
state = store(init, action)
|---------|
\|/ |
UI = (store(init, action), action)
所以想到这里,我们就可以设计一个ActionCreator模块。
来来来,上代码。
const actionCreator = ActionCreator();
actionCreator.create('INIT', (store, num: string) => {
//biz logic
store.dispatch('init', num)
});
class AppStore extends Store {
//将store绑定到ActionCreator
bindActionCreator() {
return actionCreator;
}
//除了在用actionCreator.create创建
//event handler,也可以直接在store中
@Action('INIT')
init(num: string) {
this.dispatch('init', num)
}
}
const HelloApp = () => (
<div>
<a onClick={actionCreator.fire('INIT', 1)}>爱我就赞我。</a>
</div>
)
都什么年代了 你还裸用字符串,你这是魔鬼字符串。。😓
- 是的,我们加,我们加字符串的枚举类型,一次来解决dispatch到actor等各种常量字符串
export default ActionType(
'INCREMENT',
'DECREMENT'
);
更复杂的结构,
const Actions = actionType({
PDF: 'application/pdf',
Text: 'text/plain',
JPEG: 'image/jpeg'
});