[学习笔记] Cordova+AmazeUI+React 做个通讯录 - 联系人列表(1)

[学习笔记] Cordova+AmazeUI+React 做个通讯录 系列文章

目录

  1. 准备
  2. 联系人列表(1)
  3. 联系人列表(2)
  4. 联系人详情
  5. 单页应用 (With Router)
  6. 使用 SQLite

传送门:全部章节 示例代码

了解 React 的工作方式

在阅读了大量 React 的资料(主要是官网)之后,发现有两个概念非常重要

  1. React 的基本元素是组件,所有内容都由各种组件或包含,或组合,搭建而成。
  2. React 从数据渲染页面,单向进行。这个在实践的过程中会慢慢的体会到。

Sample Mobile Application with React and Cordova 页面有一张关于 React 组件的图,说明了这个 React Demo 应用中各组件的关系(注意观察线框和箭头的颜色)。

《[学习笔记] Cordova+AmazeUI+React 做个通讯录 - 联系人列表(1)》

了解 Amaze UI React

Amaze UI React 是在 Amaze UI (jQuery版) 的基础上,抛弃 jQuery,使用 React 开发的组件库。Amaze UI React 和 Amaze UI (jQuery版) 共用一套 CSS。

就从 Amaze 官网的菜单组织也能看到二者的区别。jQuery 版的菜单包含三项:CSS、JS插件、Web组件;而 React 版就只有一项:组件。因此 Amaze UI 的 React 版不再需要用户去了解 CSS 类和过多的脚本操作,只需要关注组件,及其数据(属性和状态)即可。

开始实现通讯录列表

参考文章的通讯录包含列表页和详情页。学习得一步步进行,所以先实现列表页。

学习的目标是做一个通讯录,功能和而已都与上图类似,只是界面改用 Amaze,所以得先去 Amanze UI React 组件文档页面 了解需要使用的组件,并画出自己的草图。

《[学习笔记] Cordova+AmazeUI+React 做个通讯录 - 联系人列表(1)》

这个草图就是最初需要实现的东西:一个页头、一个列表、列表项分图标、文本、图标按钮三个部分。而图标按钮点击可以转到拨号页面准备拨号。

目标已经清楚了,下面就要开始搭建程序。第一步,先实现在 Nginx 中能正常显示;第二步再实现构建成 Android 程序在手机上使用(包括显示和拨号操作)。

代码前最后的准备

之前已经做了很充分的准备,但那主要是对环境的准备。现在马上要开始写代码,不得不做一些细致的准备,比如,把需要用到的库安放在代码中适当的位置。

之前已经确定了要使用 jQuery,React 和 Amaze UI React。按我个人的习惯,会把它放在 Web 应用的 /libs 目录下。没有必要去筛选哪些文件用得上哪些用不上,都拷贝过来,结果就有了这样的目录结构

《[学习笔记] Cordova+AmazeUI+React 做个通讯录 - 联系人列表(1)》

开始编码

修改 index.html

首先就是修改 index.html 文件,在 index.html 中引入需要的 AmazeUI 的 CSS,以及各依赖库的脚本文件。直接从默认生成的 index.html 改过来就好。

<!doctype html>
<html>

<head>
    <meta name="format-detection" content="telephone=no">
    <meta name="msapplication-tap-highlight" content="no">
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
    <title>Concats</title>

    <link rel="stylesheet" type="text/css" href="libs/amazeui/css/amazeui.min.css" />
    <link rel="stylesheet" type="text/css" href="css/index.css">

    <script src="libs/jquery/jquery-2.1.4.min.js"></script>
    <script src="libs/react/react.min.js"></script>
    <script src="libs/react/JSXTransformer.js"></script>
    <script src="libs/amazeui/js/amazeui.react.min.js"></script>

    <script type="text/jsx" src="js/index.jsx"></script>
</head>

<body>
</body>

</html>

<body> 标签中留空,因为之后 React 会将组件渲染在 <body> 中。

<head> 最后引入的 js/index.jsx 就是页面对应的脚本,使用 JSX 语法以 React 组件的形式实现。一般这个文件是以 .js 作为扩展名,但是我认为用 .jsx 作为扩展名可以清楚表明该文件中使用了 JSX 语法。

React 渲染入口

这已经是第2次提到渲染了,为什么说 React 渲染,而不说“执行”、“搭建”、“构造”诸如此类的词呢?先来看看 React 官方是怎么描述 React 的:

React is a JavaScript library for creating user interfaces by Facebook and Instagram. Many people choose to think of React as the V in MVC.

We built React to solve one problem: building large applications with data that changes over time.

简单的说,React 是一个构建用户界面的 JavaScript 库,它为构建大型应用而生,这些应用可能随时产生数据变动。很多人把 React 作为 MVC 中的 V 来使用。

而在其它文章资料中也提到,React 的处理过程是从数据到视图的一个单向过程。综合起来就可以这样理解:React 对数据进行渲染,以 UI 的形式呈现。如果数据发生变动,React 会重新进行渲染(实际上 React 会判断数据变动造成的形式,智能选择最小渲染范围以提高效率)。

上面提到将 <body> 标签内容留空,以便 React 在其中进行渲染。也就是下面这句,React 的渲染入口:

React.render(<Page />, document.body);

上述表达式用了 JSX 语法,而 JSX 中的 X 部分,我理解为是以 XML 方式描写的表达式。为什么是表达式,这会在后面的循环中提及。

正如 React 文档所述,JSX 语法只是一个语法糖,它完全可以用纯粹的脚本来写,而且 React 本身也是需要将 JSX 翻译成 JS 来执行的。上面那句话的纯 JS 写法会是这样

React.readner(React.createElement(Page), document.body);

那么 Page 是什么,这里看起来它应该是一个合法的 JS 变量——是的,这就是马上需要定义的 React 组件。

定义 Page 组件

定义组件会使用 React.createClass() 方法。根据对 React 的初步了解,我想当然的写下了一个错误的定义

var Header = AMUIReact.Header;
var List = AMUIReact.List;

// 错误的定义
var Page = React.createClass({
    render: function() {
        return (
            <Header title="通讯录" />
            <List />
        );
    }
});

React.render(React.createElement(Page), document.body);

我的原意是希望能定义 <Page /> 组件,并将其渲染在 <body> 中,只要能显示页头就行,列表部分暂时留空。

然而通过 Nginx 跑出来之后,从浏览器的控制台得到了一个错误消息

Uncaught Error: Parse Error: Line 13: Adjacent JSX elements must be wrapped in an enclosing tag

大概意思是说,JSX 的元素必须是1个封闭的标签。这里提供了两个要素:“1个”、“封闭”。所以我根据这个意思,修改了一下,然后运行出了预期的效果。

render: function(){
    return (
        <div>
            <Header title="通讯录" />
            <List />
        </div>
    );
}

上面的代码中,var Header = AMUIReact.Headervar List = AMUIReact.List 是 Amaze UI React 教程示例中演示的用法——为带命名空间的组件创建简短的名称。其实我更倾向于使用带命名空间的名称,就像 <AMUIReact.Header title="通讯录" />。这可以避免自定义组件和 Amaze UI React 组件的名称冲突。不过 Amaze UI React 定义的这个命名空间太长,可以自己缩短一下,比如

var A = AMUIReact;
<A.Header title="通讯录" />

后面的示例中就采用这种缩短命名空间的写法。

参考

对 Page 组件的解释

  1. ReactClass createClass(object specification) 根据参数提供的规格说明,创建组件类。规则说明是一个 JavaScript 对象,其提供的 ReactElement render() 函数会返回一个 ReactElement (对象)用于渲染。
  2. render() 中返回的 ReactElement 可以是 JSX 描述,也可以是纯 JS 脚本。上面的例子是用的 JSX 描述,如果改成 JS 脚本,应该像这样

    render: function() {
    return React.createElement("div", {}, [
        React.createElement(Header, {
            title: "通讯录"
        }),
        React.createElement(List)
    ]);
    }
    很明显,JSX 描述更清晰易读也更容易写出来。
    
  3. return 后面的的内容是用括号 () 包起来的,其实如果不包起来也不会出错。但很显然,在写 JavaScript 程序的时候,如果 return 的内容是多行,用括号包起来是个好习惯。

添加列表项

现在继续下一步,添加列表项。在没搞清楚如何使用循环之前,还是先用重复的代码把列表项显示出来再说——当然,在目前没有定义数据结构的情况下,也不会用到循环。考虑到对组件还不够熟悉,列表项的内容,暂时仅展示姓名。

// js/index.jsx
var A = AMUIReact;
var Page = React.createClass({
    render: function() {
        return (<div>
            <A.Header title="通讯录" />
            <A.List>
                <A.ListItem>
                    张三
                </A.ListItem>
                <A.ListItem>
                    李四
                </A.ListItem>
                <A.ListItem>
                    王麻子
                </A.ListItem>
            </A.List>
        </div>);
    }
});
React.render(React.createElement(Page), document.body);

《[学习笔记] Cordova+AmazeUI+React 做个通讯录 - 联系人列表(1)》

这个结果并不好看,但至少已经实现了列表的显示。待功能完善之后还不能达到满意的效果,可以自定义 CSS 来调整,所以不急。

引入 JSON 数据并循环处理列表项

数据当然不会是固定不变的,直接将列表项写死非常不切合实际。在学习初期,我暂时还不想去和数据库打交道,所以暂时用 JSON 来保存数据,而且为了保持获取数据不节外生枝,先把数据定义在 index.jsx 的最前面。

// js/index.jsx
var data = [
    {
        "name": "张三",
        "tel": "13801234567"
    },
    {
        "name": "李四",
        "tel": "18018001800"
    },
    {
        "name": "王麻子",
        "tel": "17098765432"
    }
];

var A = AMUIReact;

// ... 后面的代码略

然后改 render(),想当然的又写了个段错误代码

// 错误的代码
render: function() {
    return (<div>
        <A.Header title="通讯录" />
        <A.List>
            for (var i = 0; i < data.length; i++) {
                <A.ListItem>{data[i].name}</A.ListItem>
            }
        </A.List>
    </div>);
}

这回从错误消息中可以发现是 for 循环惹的祸。一开始不明白为啥,仔细思考之后明白了——return 后面的应该是一个表达式,而 for 循环不是表示式。于是尝试把 for 语句改成 Array.prototype.map() 方法

render: function() {
    return (<div>
        <A.Header title="通讯录" />
        <A.List>
            {data.map(function(t) {
                return (
                    <A.ListItem>{t.name}</A.ListItem>
                );
            })}
        </A.List>
    </div>);
}

这回是对了,但是这写法看起来不够简洁,容易把人搞晕。参考网上的资料,发现可以将 map() 的结果保存在一个变量中,再在 <A.List> 中引用

render: function() {
    var items = data.map(function(t) {
        return <A.ListItem>{t.name}</A.ListItem>;
    });

    return (<div>
        <A.Header title="通讯录" />
        <A.List>
            {items}
        </A.List>
    </div>);
}

小结 JSX 中如果在代码中嵌入成对的 <XML标签>,会被翻译成组件代码(React.createElement(...) 等),而在 xml 标签中使用一对大括号 {} 可以嵌入 JS 代码。如果对 ASP.NET MVC 的 Razor 模块有所了解,就会有似曾相识的感觉。

未完待续

接下来还要为列表项添加图标,以 React 的组件化思维来思考,比较好的作法是定义一个组件,不妨叫 Person 来封装图标、姓名和电话按钮。那么,如何将每个人的数据传入 Person 对象?

另外,把数据放在 index.jsx 中也不是个办法,迟早还是得用异步(比如 Ajax 或从数据库获取)加载的,又该如何将数据更新到页面中?

欲知后事如何,且看下回分解!

    原文作者:边城
    原文地址: https://segmentfault.com/a/1190000003797052
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞