钉钉开发指南-Nowa模板&命令详解篇

nowa init page

这是根据本人项目需求和个人习惯,定制的脚手架模板. nowa init 和nowa的定制模板, 是开发效率倍增的利器.

  • 你可以定义自己的代码模板, 详细请了解nowa的官方文档
  • 你可以定义多个代码模板,用于不同的用途. nowa init page | nowa init pageA | nowa init pageB ….
    1
    nowa init page

index.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
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Route } from 'react-keeper'
import PageHome from 'pages/home/';
/*
App为顶层组件,与index.html的<div id="App"/>所对应, 由ReactDOM.render灌入.
*/
class App extends React.Component {
render() {
return ( <div> {
this.props.children //指向的就是 <PageHome.route />
} </div> );
}
}
/*
* <HashRouter>方式,是一个单页应用, 请勿换其他模式,否则钉钉的jsapi鉴权处理麻烦了.
* 当前demo中,home页面组件包含有tabbar, 就意味所有home下面的页面都带有这个组件.
* 如果想其他组件不带有tabbar, 可以到顶层来配置.脱离home.
*/
const rootRoute = <HashRouter><div>
<Route name="app" path="/" component={ App } >
<PageHome.route />
</Route>
</div></HashRouter>
ReactDOM.render( rootRoute, document.getElementById('App') );

末层路由

模板默认生成,无需修改.

1
2
3
4
5
6
7
8
import { Route } from 'react-keeper'
const PageNav= {
page : require( './PageNav' ) ,
route : ()=>{ return ( <Route index component={ PageNav.page } path= '/nav' /> )}
}
export default PageNav


嵌套路由

手动添加: 导入子页面和路由嵌套项目

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
import { Route } from 'react-keeper'
/* 导入子页面组件
* 用于tabbar的切换加载
* 子页面放在./pages目录里面,也可以不用目录直接放子页面组件的文件夹
*/
import PageNav from './pages/nav';
import PageEntry from './pages/entry';
import PageDisplay from './pages/display';
import PageFeedBack from './pages/feedback';
import PageOther from './pages/other';
/* 配置本层路由表
* 这是一个嵌套路由的demo
* 为什么路由规则中,多了一个'index'属性? 请查看react-keeper官方文档的更多用法,或者react-router.
* 嵌套形成的url路由为: /home/nav (注意./pages/nav/index.js的规则)
*/
const PageHome= {
page : require('./PageHome') ,
route : ()=>{ return (
<div>
<Route index component={PageHome.page} path= '/home' >
<PageNav.route />
<PageEntry.route />
<PageDisplay.route />
<PageFeedBack.route />
<PageOther.route />
</Route>
</div>
)}
}
/* 默认暴露PageHome,其他的对象自然就对外不可访问了.
export default PageHome


并行路由

手动添加: 导入子页面和路由嵌套项目

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
import { Route } from 'react-keeper'
/* 导入子页面组件
* 用于tabbar的切换加载
* 子页面放在./pages目录里面,也可以不用目录直接放子页面组件的文件夹
* PageHome中的{this.props.children },随着/home/nav | /home/entry 的访问,装载不同的子页面组件.
*/
import PageNav from './pages/nav';
import PageEntry from './pages/entry';
import PageDisplay from './pages/display';
import PageFeedBack from './pages/feedback';
import PageOther from './pages/other';
/* 配置本层路由表
* 这是一个嵌套路由的demo
* 为什么路由规则中,多了一个'index'属性? 请查看react-keeper官方文档的更多用法,或者react-router.
* 并行形成的url路由为: /hoem | /nav 各自独立,
*/
const PageHome= {
page : require('./PageHome') ,
route : ()=>{ return (
<div>
<Route index component={PageHome.page} path= '/home' />
<PageNav.route />
<PageEntry.route />
<PageDisplay.route />
<PageFeedBack.route />
<PageOther.route />
</div>
)}
}
/* 默认暴露PageHome,其他的对象自然就对外不可访问了.
export default PageHome

PageHome.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
37
38
39
40
41
42
43
44
require('./PageHome.less');
import Icons from 'assets/icon'; // 导入svg icon, 具体可以看src/assets/icon/index.js, 按例子添加svg图标即可.
import logic from './PageLogic'; // no-flux的状态管理模块, 不可修改
import { Control } from 'react-keeper';
import { Component, LogicRender } from 'no-flux'; // no-flux的封装组件模块, 不可修改
import TabBar, { activeTabbar } from 'components/card-tabbar'; //导入自行封装的tabbar
class Home extends Component { // no-flux的组件类,不是react的组件类
constructor(props) { super(props, logic); // PageLogic.js ,在这里给传入了.
this.handleChange = this.handleChange.bind(this);
}
handleChange(key){
this.execute('setTabbarIndex',key); // 用this.execute将状态的处理转到了PageLogic对应的函数进行处理.
Control.go(this.state.menu[key].path, ); // react-keeper的url跳转
}
render() {
const { menu, tabbarIndex, badge, } = this.state; //别忘记有什么状态了.
const activeIndex=activeTabbar( menu ); //针对点击钉钉导航栏返回按钮,导致tabbar激活错位的处理.
if (tabbarIndex != activeIndex ){ // 对比url索引和当前选中的值,如不对应则纠正.
this.execute('setTabbarIndex',activeIndex);
}
return (
<div className="home">
{this.props.children } // 路由访问/home/nav, 这里就加载nav.
// 请查看 src/components/card-tabbar组件,会对React有了更深层次的理解.
<TabBar menu={menu} tabbarIndex={tabbarIndex} badge={badge} onChange={this.handleChange} />
</div>
);
}
componentDidMount() {
// 终于看到用钉钉jsapi了, 当真机调试情况, 会看到导航栏的标题被这里改变了.
dd.biz.navigation.setTitle({ title:'Home' })
}
}
export default Home ;

PageLogic.js

src/pages/home/pages/display/PageLogic.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
37
38
39
40
41
42
43
44
45
46
47
48
import Ding from 'dings';
import { assign } from 'lodash'; // lodash 值得去学习.js的小函数库
import { apiSync } from 'utils' // 异步访问api资源变同步的小函数
import PageConst from './PageConst'; // 导入附加的状态.
export default {
defaults(props) {
return { empty: true, loading: false, ...PageConst, // ...PageConst,是js的用法, 展开这个对象.
buttonText: ' 当前页面: display ', // 初始的状态
inputText: '钉钉微应用点击 (手机端)}', //初始的状态
}
},
/* 详细去看no-flux官方文档, 本模块所有的函数都会绑定方法和对象
setButtonText和value: 对应this.execute('setButtonText','abcd')
{setState}: 这个好奇怪呀,从哪里来的?
setButtonText( obj, value) {
console.log(obj) //写成这样,你打印到浏览器控制器,会发现好东东
setState({ buttonText: value })
},
*/
setButtonText({setState}, value) {
setState({ buttonText: value })
},
printState({ getState }) {
console.log( '当前状态:', getState() )
},
// 这个例子是,将钉钉jsapi的链式回调,写成了同步用法,简洁好理解了吧.
async inputText({setState, getState}) {
const p = { message: "随便输入写什么吧!", title: "提示", buttonLabels: ['确定', '取消'], };
const res = await Ding.sync( dd.device.notification.prompt, p );
setState({ inputText: res.value});
}
/*
// 这个例子是用get方法访问api资源. 以后另起篇章详解. 具体参考 unity-router的用法.
async getUser({ setState, Api, }) { // Api数据请求
setState({ loading: true });
let state = await apiSync( Api.user.get, {},)
setState( _.assign(state,{ loading: false }));
},
*/
};

  • React 单向数据流的变化

    1. page.js => onClick = { this.handlClick} // 触发点击事件
    2. page.js => this.execute ( ‘setTitle’, ‘钉钉教程’ ) //执行状态处理
    3. logic.js => setTitle ( { setState }, p ) // 对需要改变的状态进行逻辑处理
    4. logic.js => setState({ buttonName: p }); // 赋值状态值,并触发了组件的渲染,按钮名字发生了改变
  • 在React中,数据的流向是单向的. 一定要理解清楚属性和状态的关系:
    比如我们有个对象, 赋予它属性: 玻璃, 它就具备了玻璃的状态, 赋予它空心圆柱体, 它就有了外观形态.这就是一个玻璃水杯.如果我们赋予它属性: 塑料, 其他不变, 这就是塑料水杯了. 所以,要修改React的组件状态,在外部(上一层)的通过这个组件的props(属性),传到 了组件的内部, 内部再对属性进行logic(逻辑)处理,就像固化程序的流水线一样, 做加工处理,就得到我们想到的结果.

nowa init mod | rmod

react创建组件的三种方式及区别

注释部分

除了方便阅读外, 主要为了方便复制粘贴了,

1
2
3
4
5
6
7
8
9
10
/* import Tabbar, { activeTabbar } from 'components/card-tabbar';
<Tabbar menu={ } tabbarIndex={ } badge={ } onChange={ } />
menu: [{"title":"导航", "icon":"nav", "path":"/home/nav" },]
tabbarIndex: 选中状态索引
badge: 徽标数
onChange: 触发修改事件对应的函数
https://mobile.ant.design/components/tab-bar/
*/

const 定义

演示了组件外部的写法, 嵌套过于深的组件,这样外部写,更简洁清晰.

1
2
3
4
5
6
const Item = List.Item;
const CustomChildren = (props) => (
<div >
<%- Name %> component
</div>
);

map用法

演示了map循环渲染多个子组件的用法, 注意?这个三元操作符.

  • 为啥用map循环,不用for? 因为在混合代码里面不能用for这个同步循环命令.
  • 为啥我的json对象,不能map? map仅仅支持数组和map的格式,(本人被这个坑了,纠结了1星期)
    1
    2
    3
    4
    5
    6
    7
    <List> {
    data ? data.map((item, key)=> {
    return (
    )
    }) : '' ;
    }</List>

nowa init api

配置api资源

src/config/develop.json

  • API_URL: 真实的api服务的url前缀
  • MOCK_URL: 模拟数据的api服务的url前缀

增加api资源

1
nowa init api

配置api资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const user ={
user: {
namespace: 'users/',
methods: { 添加各自restful api的方法
get: ( ) => ({ path: '' }),
add: (data) => ({path:''},{options:{
method:'POST',
body:JSON.stringify(data),
}}),
}
}
}
export default user

绑定api资源

src/apis/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import { assign } from 'lodash';
import user from './user'; //导入一个api资源
export const resources = assign( {} ,
user, //绑定资源到api对象
);
export const fetchOptions = {
method: 'GET',
mode: 'cors',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', },
credentials: 'omit'
};

使用资源

PageLogic.js: 这是一个同步化的结构, 使用了ES7的async/await的特性, 可读性更强.

1
const state= await apiSync( Api.area.get, [ userInfo.corpId, userInfo.emplId ], { fit:true, } )

apiSync( Api.area.get, [ userInfo.corpId, userInfo.emplId ], { fit:true, } )
第一个参数: api资源对象
第二个参数: 数组结构 post提交的值
第三个参数: json结构,使用中间件处理 (当前可用: fit log mock )