开发政府网站使用React,还兼容了IE8

#1

谈使用React开发政府网站,还兼容了IE8的过程

围观地址: 青岛市居民健康信息服务平台(区域诊疗一号通) http://guahao.jkqd.gov.cn/

大家看到政府网站使用React技术有惊讶!

“政府网站用 React, 毫无 PS 痕迹啊.“”

“政府网站居然使用react,而不是asp, 真的挺惊讶的”

这说明我们是一个有理想的创业公司,至少是一个有理想的创业团队,也肯定是一个有理想的前端团队吧。哈哈

背景、准备

在做这次改版的前一个月,我们就在思考使用什么技术,并在技术上制定了几个目标: 体验超过大部分同类网站最好是单页面的(相应速度比较快)有技术挑战能够沉淀来应用到其他业务的团队成员能自豪的在简历上添注一笔

首先派出团队成员A君(喜欢新技术犹如看到漂亮妹子),开始做技术选型、搭建基本的DEMO,这个过程比较漫长,选型也是逐步完成,每天的进度比较慢,需要考虑的点很多,特别是在熟悉 react 技术的过程中,半个月以后临近正式投入才决定使用 React + Redux + webpack + ES6语法,不过 Redux一直是我们选择过程中纠结地方, 最后在查看DEMO的时候发现 Redux 的思维太高级(看不懂), 对于一个完全没有接触过 React 的团队,要同时接受 React 和 Redux 的思想很困难,可能会让项目进度无法把控,所以最后决定这个项目暂时先不使用 Redux 。

开始

分工
  • DEMO

    先DEMO, 后逻辑 这是我们约定的一个基本原则,首先把设计稿PSD全部转换成HTML静态DEMO,并做好页面之间的链接,这有助于我们在前期就掌握网站的大多数模块,那些可复用,哪些是独立的。也可以提前让设计师和产品发现设计上的缺陷,及时改进。为此我们在本地在启动webpack的时候也会启动一个静态服务器express并做了一些特殊规则(使用ESSI,LESS插件等),预览DEMO。也会把demo部署到内部环境让所有人预览

  • 工作量按页面分工

    每个人分页面开发,但在开发自己页面之前需要协调好一些公共模块是不是其他页面已经有了,不做重复工作。这个过程具体的开发人员之间各自协调

  • 技术问题分工

    在正式大规模投入React模块逻辑开发之前,有2个问题需要提前解决 低端浏览器兼容(IE8) 整个项目的代码组织方式,第一个问题交给A君(如何让React兼容IE8,敬请期待),后一个我自己来解决(ReactPC网站整体代码继承结构

GIT与代码部署
  • 本地测试环境

    不用多说,直接用webpack + express 来预览,在后端API还未完成的初期使用http://rap.taobao.org/org/index.do来模拟接口。

  • 内部环境和线上环境的部署

    代码部署完全交给 git webhook 来完成(之前的基础设施已具备), 内部环境部署 git push origin ${branch} , 线上部署 git push origin ${tag}。所有部署动作都是自动化完成的,工程师只需要提交代码到gitlab仓库即可。

  • git分支管理

    并没有每个人独立分支开发,而是3个人在同一分支上同时开发,因为之前的分工明确、开发人员也只有3个,开发过程并没发生代码冲突导致合并困难, 反而直接合并(git pull)其他成员的提交,让协作变得很快,公共模块的更新能及时的反应本地环境来,所依赖的其他人的任务也可以第一时间合并进来。

开发中

在开发中我们反复纠结了代码组织方式,以及模块数据的来源。期间也经历了好几次观念上的升级,都会导致代码组织方式发生变化,不过这些变化都还算愉快,一直在思考问题更好的解决方案,从问题取得进步

代码结构

依靠下面结构解决了以下问题

  • 所有模块的加载中状态
  • 所有模块的接口重试机制
  • 单个模块报错影响面积最小化
  • 合并不同模块对同一API的多次调用
  • 最大限度做到接口缓存,提高二次访问速度
  • 页面级事件的处理(比如登出,登入)

  • BaseModule 的 render

    renderLoading(){
        return <div className="no-data">
            <img src="http://gtms04.alicdn.com/tps/i4/T1hPyYFD0kXXa679Pe-40-40.gif" width="20" />
        </div>
    }

    renderIOError(){
        return (<div onClick={this.componentDidMount.bind(this)}
                         className="no-data">{this.state.msg || "请求失败"}</div>)
    }

    renderError(){
        return <div className="no-data" onClick={this.componentDidMount.bind(this)}>数据格式不正确</div>
    }

    render() {

        if (this.state.loading) {
            return this.renderLoading()
        }

        if (this.state && this.state.success == false) {
            return this.renderIOError()
        } else {
            try {
                return this.toRender();
            } catch (e) {
                if (navigator.userAgent.indexOf("MSIE 8.0") == -1){
                    console.log(e, this);
                }
                return this.renderError()
            }
        }
    }

  • 【源码】科室列表模块的代码

import React from 'react';
import ReactDOM from 'react-dom';
import BaseModule from '../../libs/BaseModule';
import {Link} from 'react-router'



export default class DepartmentList extends BaseModule {

    constructor( props ){

        super( props )
        // 从 url 或者 父级获取参数
        this.corpId = this.props.corpId || this.query.corpId;

    }

    componentDidMount(){
        //请求数据,从缓存中
        this.getCache("/user-web/ws/query/dept-list2", {
            corpId:this.corpId
        })
    }

    renderLoading(){
        //使用一个定制的加载状态
        return <div>
            <Placeholder />
            <Placeholder />
            <Placeholder />
            <Placeholder />
        </div>
    }
    toRender(){
        //请求完成渲染
        let corpId = this.corpId;

        return (<div>
                {   
                    this.state.data.map((item, index) => {
                        return (<div key={index} className="department-list">
                            <dl className="department-big-list">
                                <dt><span className="title">{item.deptName}</span></dt>
                                <dd>
                                    <ul>
                                        {
                                            item.children.map((item, index) => {
                                                return <li key={index}><Link to={"/yuyue/department-index?deptCode="+item.deptCode+"&corpId="+corpId}>{item.deptName}</Link></li>
                                            })
                                        }
                                    </ul>
                                </dd>
                            </dl>
                        </div>)})
                }
            </div>)
    }

}

//给当前模块一个漂亮的加载中状态
class Placeholder extends React.Component {

    render(){
        return <div className="department-list placeholder">
            <dl className="department-big-list">
                <dt><span className="title"></span></dt>
                <dd>
                    <ul>
                        <li><a></a></li>
                        <li><a></a></li>
                        <li><a></a></li>
                        <li><a></a></li>
                        <li><a></a></li>
                        <li><a></a></li>
                        <li><a></a></li>
                    </ul>
                </dd>
            </dl>
        </div>
    }
}



引入 react-router

开发到中期我们惊讶的发现,整个网站的入口竟然被自然的统一到一个模块中(App), 只需要把这个模块渲染到页面就可以了,单页面的火种又重新燃起了,于是在项目中新建了一个分支(为什么和这个时候要开新分支?)来引入react-router, 为了兼容IE8,所以使用 hash 的方式。引入以后对已经开发好的模块还是有一定影响,因为状态被放在hash上了,以前代码中处理这部分的逻辑需要重构。比如需要保留状态的选项卡,就必须把状态放在路由中处理,而不用自己处理状态,选项卡主模块只简单renderthis.props.childer就好,像一个容器。

  • 为什么和这个时候要开新分支?

    评估到这个时候引入 react-router 可能存在风险,万一不成功还可以丢掉,故开新分支。

  • App的render 和 依赖的 history


import {Router,Route, IndexRoute, IndexRedirect, browserHistory,hashHistory,useRouterHistory } from 'react-router'

import {createHashHistory} from 'history'

const history = useRouterHistory(createHashHistory)({queryKey: false})



    render() {
        return (
        	<Router history={history}>
			    <Route path="/" component={Page}>
			        <IndexRedirect to="index"/>
			        <Route path="index">
			            <IndexRoute component={IndexScreen}/>
			            <Route path="search" component={SearchScreen}>
			                <IndexRoute component={All}/>
			                <Route path="jib" component={Jib}/>
			                <Route path="zzk" component={Zzk}/>
			            </Route>
			        </Route>
			        <Route path="yuyue">
			            <IndexRoute component={CorpListPage}/>
			            <Route path="corp-list" component={CorpListPage}/>
			            <Route path="corp-index">
			                <IndexRedirect to="keshi"/>
			                <Route path=":activeKey" component={CorpIndex}/>
			            </Route>
			            <Route path="department-index" component={DepartIndexScreen}/>
			            <Route path="doctor-index" component={DoctorIndexScreen}/>
						<Route path="doctor-list" component={DoctorListScreen}/>
			            <Route path="schedule-confirm" component={ScheduleConfirmScreen}/>
			            <Route path="schedule-success" component={ScheduleSuccessScreen}/>
			        </Route>
			        <Route path="news">
			       

模块的数据来源

什么样的模块自己发请求,什么样的模块使用父级传递的数据渲染。这也是 redux 说说的智能模块笨模块的区分,当然最终没有什么好的定论,毕竟这确实是一个设计哲学的问题,每个人都有不一样的标准。下一次我们也会尝试 redux, 看看他的解决方案。

统计
资源 数据
前端开发 3人(非全投入)
开发周期 1个月
模块数量 60+
最后广告时间

团队招人,坐标杭州,待遇优厚,特别优秀的工程师可以谈期权。

我们正在实践react, react-native,绝对能把最新的技术运用到生产环境。
给你最适合技术探索的环境,不信问A君。

团队缺前端工程师,android工程师和ios工程师

跪求简历 mail: lujun@yuantutech.com

React小白的入门兼容性疑惑
#2

是否有开源 组件呢

1 Like
#3

能发一个最小的demo吗? 比如两个频道导航的例子

#4

文章不错, 不过 Markdown 语法标记的代码没写对, 搜一下 “GitHub flavored Markdown”

#5

好像是 这个网站支持得太差了吧! 原文在 github 上很整齐

#6

Discourse 系统用的 CommonMark 也是按照标准做的, 可能是 GitHub 的问题. Markdown 有不止一个版本.

#7

我想问下博主用的是react的什么版本呢?

#8

请问这个网站的后端是用的什么技术?

#9

ie 不行呀

#10

请问react-router使用什么版本啊?

#11

怎么兼容的啊

#13

可以使用我们公司的迷你React框架anujs https://github.com/RubyLouvre/anu

高级兼容React,能运行antd 99% 的组件,无缝切换React项目,在IE8下也能无痛运行

#15

大佬你好,请问你们公司的anujs框架能兼容IE7吗?我们公司有个项目业主要求兼容到IE7。。。

#16

你们可以试试,理论上可以,主要你没有用到defaultValue, defaultChecked。这两个是非受控属性,用到Object.defineProperty