Hello, plume2.
import React, { Component } from 'react'
import {Actor,Store,StoreProvider,Relax,ViewAction} from 'plume2'
//MapReduce
class HelloActor extends Actor{
defaultState(){
return {text:"Hello,plume2"}
}
}
//reactive ui event 反应式ui事件
class AppViewAction extends ViewAction{
sayHello = (text:any) =>{
this.store.dispatch('say:hello',text)
}
}
// Single Data Source 单一数据源
class AppStore extends Store{
//bind data transform 绑定数据转换
bindActor(){
//after plume2 directly pass Actor class 直接通过Actor类
return [HelloActor]
}
//bind ui event 绑定ui事件
bindViewAction(){
return {
AppViewAction
}
}
}
//Auto compute relaxProps 自动计算打开Props
@Relax
class Text extends React.Component{
static relaxProps = {
//auto injected by store.state().get('text') 由store.state().get('text')自动注入
text:"text",
//auto injected by store's bindViewAction 由store的bindViewAction自动注入
ViewAction:'viewAction'
}
_handleClick = () =>{
const {text,viewAction} = this.props.relaxProps
viewAction.AppViewAction.sayHello(text)
}
render(){
const {text,viewAction} = this.props.relaxProps;
return (
<div onClick={this._handleClick}>{text}</div>
)
}
}
@StoreProvider(AppStore) //应用程序入口
export default class Index extends Component {
render() {
return (
<div>
<Text/>
</div>
)
}
}
API Actor
Actor calculation model, an independent calculation unit, whose main function is to transform our state data
import { Actor,Action} from 'plume2'
// 是的 这就是一个Actor简单世界
class HelloActor extends Actor{
// 领域的初始数据 该数据会被自动的转换为immutable
defaultState(){
// 返回的对象会被自动的转化为immutable
// 除非有特殊数据结构如(set OrderedMap之类)
//不需要特殊指定immutable数据结构
return {text:'hello plume2'}
}
//** 通过@Action来建立store的dispatch和actor的handler(处理程序)之间的关联
// API规范
// @param state actor上一次的immutable状态
// @param text store dispatch的参数值 尽量保持单值设计
@Action('change:text')
change(state,text){
// immutable api
return state.set('text',text)
}
}
Store
What is Store?
Store Our data state container center manages the life cycle of the entire app's data
We adhere to the idea of single data source (single data source), and maintain a complete business and UI state in the store
The main responsibilities of Stor:
1 Aggregate actors
2 dispatch actor (single dispatch transaction dispatch)
3 Calculate our query language (QL/PQL) through bigQuery
4 Responding to page events (ViewAction)
5 Register to respond to RL
import {Store,ViewAction} from 'plume2'
import LoadingActor from 'loading-actor'
import UserActor from 'user-actor'
import TodoActor from 'todo-actor'
// 响应页面事件的逻辑处理
class AppViewAction extends ViewAction {
//show simple dispatch 显示简单分派
// when dispatch finished if status had changed, 如果状态已更改,则在调度完成时,
//each Relax component received message 每个组件都接收到消息
update = () => {
//将计算的任务分派的到actor
//然后根据actor的返回值 重新聚合新的store的state
//该为单分派 当dispatch结束 store的state发生改变的时候
//UI容器组件(StoreProvider,Relax) 会收到通知重新re-render UI
this.store.dispatch('update')
}
// show multiple dispatch in a transaction 在事务中显示多个分派
save = () =>{
//事务分派
//很多场景下 计算应该是原子类型的 我们想一想 dispatch 结束才通知UI去re-render
//这个时候我们就可以开启事务控制
// transaction 会返回值来判断在dispatch 过程中有没有发生错误
// 如果发生错误 数据会自动回滚到上一次的状态 避免脏数据
// 我们也可以指定 自定义的回滚处理
// this.transaction(()=>{/*正常逻辑*/},()=>{/*自定义的回滚函数*/})
this.store.transaction(()=>{
this.store.dispatch('loading:end')
//这个地方可以得到上一次的dispatch之后的结果
//如:
const loading = this.state().get('loading')
this.store.dispatch('init:user',{id:1,name:'plume2'})
this.store.dispatch('save')
})
}
}
class AppStore extends Store{
// 聚合Actor
// 通过reduce各个actor的defaultState
// 聚合出store的state作为source data
bindActor(){
//plume2 直接传递Actor的class
return [LoadingActor,UserActor,TodoActor]
}
bindViewAction(){
return{
AppViewAction
}
}
}
Store public-API
// 绑定需要聚合的Actor
bindActor(): Array<Actor | typeof Actor>
bindViewAction(): IViewActionMapper
// 事务控制dispatch
// dispatch: 正常逻辑
// rollBack: 自定义回滚逻辑 默认是自动回滚到上一次的状态
//返回是否发生回滚
transaction(dispatch:Dispatch,rollBack:RollBack):boolean;
// 计算QL
bigQuery(ql:QueryLang):any
// 当前store聚合的状态
state():IMap;
// 定义store状态更新通知
subscribe(cb:Handler):void;
// 取消订阅
unsubscribe(cb:Handler):void;
StoreProvider
StoreProvider container component connects our React component and AppStore. Provide data sources to React components
The main tasks in StoreProvider are:
1 Initialize our AppStore
2 Bind AppStore objects to the context of React components
3 Relay is the store object obtained through the context
4 Monitor store state changes
Friendly reminder: We also provide debug mode
Turn on the debug mode and we can track the data across the entire link
Track the processing of the dispatch actor of the store, the calculation of QL by relax, etc.
import React, { Component } from 'react'
import {StoreProvider} from 'iflux2'
import AppStore from './store'
//enable debug
@StoreProvider(AppStore,{debug:true})
class ShoppingCart extends Component{
render(){
return(
<Scene>
<HeaderContainer/>
<ShoppingCart/>
<BottomToolBarContainer/>
</Scene>
)
}
}
Relax
Relax is a very important container component in plume2, similar to dependency injection of Spring container
The core function will be based on the data declared in the relaxProps of the subcomponent,
The value of the property is calculated intelligently and then transparently transmitted to the sub-component as this.props.relaxProps
In order to solve the problem of verbose transmission of React's props layer by layer
Calculation rules
1 The value of the state of the store, which directly gives an immutable path, such as count:'count',todoText:['todo',1,'text']
2 The method of the store has the same name as the method. For example: destroy:noop, we prefer to use ActionCreator to handle the side effect of the UI separately
3 If the attribute is'viewAction', directly inject the ViewAction bound in the store
4 If the attribute is the result of QL injection QL calculation, if PQL will automatically bind the context of the store
@Relax
export default class Footer extends React.Component{
static relaxProps = {
changeFilter:noop,
clearCompleted:noop,
count:countQL,
loadingPQL:loadingPQL,
filterStatus:'filterStataus',
viewAction:'viewAction'
}
render(){
const{
changeFilter,
clearCompleted,
count,
filterStataus,
viewAction
} = this.props.relaxProps
}
//...
}
QL/PQL
Why do we need a QL
1 We regard store state as source data, because the data displayed by the UI may need to be combined according to our source data
2 We need the UI data to be reactive. When the source data changes, @Relax will recalculate our QL
3 Imperative programming. Manually and accurately handle the dependence and update of the data. Reactive will automatically handle the dependence of the data. However, the same QL may be executed multiple times, resulting in computational waste.
However, there is no need to worry about QL supporting cache to ensure that QL will not double-calculate when the data corresponding to path has not changed.
QL = Query Lang
The source of the custom query syntax data is the data returned by the state of the store
Syntax QL(displayName,[string|array|QL...RelaxContainer,fn])
displayName, mainly to help us better log tracking in the debug state
string array QL: string array is the path of immutable get, QL other QL (support wireless nesting)
fn: The callback function bigQuery that can calculate the state will get all the values corresponding to the path in all the arrays and pass them to fn as a parameter
// 返回:{
// id:1,
// name:'iflux2',
// address:{
// city:'南京'
// }
// }
store.state()
//QL计算的结果值是'iflux2南京'
const helloQL = QL('helloQL',[
'name',
['address','city'],
(name,city)=>`${name}${city}`
])
Store.bigQuery(helloQL)
QL in QL
import {QL} from 'plume2'
const loadingQL = QL('loading',['loading',loading=>loading])
const userQL = QL('userQL',[
//query lang 支持嵌套
loadingQL,
['user','id'],
(loading,id)=>({id,loading})
])
We have made some major changes in version 0.3.2
plume2 is a new starting point for us. plume2 is the starting point for us to typescript. plume2 is completely thinking about the characteristics and implementation of the framework from the static and compilation perspectives of typescript. We hope that plume2 is light and simple and consistent, while giving elegant code inspection error prompts.
The log tracking of the whole link wants our development to be easier
In our practice, there will be some places that are not detailed enough. We need to constantly improve. How to think about improvement is not overriding. The development experience is as important as the user experience.
inprovements
1 Kill DQL, DQL is a bit tasteless. This is the reality. There is an ideal difference. The replacement of template variables that require dynamic recursion in the implementation process is also more guilty.
More importantly, the source of DQL's dynamic data can only be the props of React's Component, which brings some inflexibility and limitation. We design DQL.
Or what QL's original intention is to obtain data declarative (Declarative) and the data itself reactive (reactive) In order to solve this problem, we designed a simpler
PQL(partial Query Lang)
import {PQL,Relax} from 'plume2'
const helloPQL = PQL(index =>QL([
['user',index,'name'],
(name)=>name
]))
@Relax
class HelloContainer extends React.Component{
static relaxProps = {
hello:helloPQL //自动绑定store的上下文
}
render(){
const value = hello(1)
return <div>{value}</div>
}
}
Simple and clear realization of more flexible parameter entry does not currently support QPL nested QPL.
1 More comfortable development experience
Sometimes, in order to quickly test some of our store methods in the browser console such as (chrome console), we will write
class AppStore extends Store {
constructor(props: IOptions){
if(_DEV_){
window['store'] = this
}
}
}
In this way, you can directly call _store in the console to quickly test, but it is a little annoying to write each page like this often.
Write a construction method for no reason. It's also quite boring.
In some SPA or react-native multi-page _store will be repeatedly covered.
This problem can be solved from the framework level. The framework is automatically bound when the debug specific of the application is turned on. To simplify this process
开启debug-mode
@StoreProvder(AppStore,{debug:true})
class HelloApp extends React.Component{
render(){
return <div/>
}
}
plume2 will automatically bind _plume2App to the window, and each key is the component name under storeprovider
This way the little friends can play as much as they want.
1 Better event handling module Currently, the handlers of our UI interaction events are all in the store, because we hope that the UI is less-logic so that it can be universal to our business layer.
Previously, the method of injecting our store through relax and relaxProps gave the UI interaction logic such as:
const noop = ()=>{}
@Relax
class HelloApp extends React.Component{
props:{
relaxProps?:{
OnInt:Function;
onReady:Function;
onShow:Function
}
}
static relaxProps = {
onInit:Function,
onReady:Function,
onShow:Function
}
}
This feature is simply injected by relax and it's done. There is a rule as long as the name of the method is the same as the method name of the store. But in actual operation, I found that writing the injection and then writing the typeScript type definition is really too heavy and exhausting.
What's more, we may have a leaf node component that just wants to call back an event and must be injected through relax. If there is an improper design of the scene of the list data,
If each item is a relax page with 200 pieces of data, it is a 200relax component.
The essence of relax is to subscribe container Component,
It will monitor the changes of the store in real time and the performance of the 200 crashing performance drops.
SO We need to think about how to define UI UI = s(state) in react. In fact, this is not complete. This only defines the display part of the UI and interactive interaction. Functional view events are side effects, so a more complete definition should be UI. = f(state,action),
Continue thinking, is it state? From the perspective of flux, look at state = store(initState, action). Are you familiar with all actions? Is there any connection? In fact, one is the exit and the other is the entrance
So with this in mind, we can design an ActionCreator module.
Come, come, code.
const actionCreator = ActionCreator()
actionCreator.creat('INIT',(store,num:string)=>{
//biz logic
store.dispatch('init',num)
})
class AppStore extends Store{
//将store绑定到AcitonCreator
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>
}
One problem with this approach is that ActionCreator is a singleton. This will cause the event to be overwritten by the context of the store when a page is repeatedly rendered multiple times.
Based on this consideration, it is still through context injection binding, so ViewAction was designed in 1.0.0 to solve this problem
ViewAction
import {ViewAction,Store} from 'plume2'
class LoadingViewAction extends ViewAction{
loading = () =>{
this.store.dispatch('loading:start')
}
}
class FilterViewAction extends ViewAction{
filter = (text:string) =>{
this.store.dispatch('filter:text',text)
}
}
//bind to store
class AppStore extends Store{
bindViewAction(){
return{
LoadingViewAction,
FilterViewAction
}
}
}
//how to injected to ui
class Filter extends React.Component{
props:{
relaxProps?:{
//代码自动提示 参考example中的例子
viewAction:TViewAction<typeof {LoadingViewAction,FilterViewAction}>
}
}
static relaxProps = {
viewAction:'viewAction'
}
render(){
const {viewAction} = this.props.relaxProps
}
}
What age are you still using strings naked, you are the devil string. .
Yes, we add the enumeration type that we add the string to once to solve the dispatch to the actor and other constant strings
export default ActionType('INCREMENT','DECREMENT')
More complex structure,
const Actions = actionType({
PDF:'application/pdf',
Text:'text/plain',
JPEG:'image/jpeg'
})
So just use it directly, it is recommended to use constant string enumeration, why?
export const enum Command{
LOADING="loading",
FILTER_TEXT="filter:text"
}
No gold is pure, no one is perfect, in reality
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。