吐槽

公司自己的产品,由于历史遗留问题,前端一直是和java放到一个项目里写的。

导致了,前端就被死死的绑在了IDEA战车上。想要看页面效果,先起几个java服务。想要调试一个改动,重启个java服务先。(热更新是有,但是间歇性失效,会给调试带来意想不到的困扰。)

选择 React.js 的原因

打算做前后分离,之前的技术路线是 Vue.js 多页。想多掌握些技能,对现有产品的结构,进行一次改革,解放前端。(产品的代码量已经不小了)于是咨询大佬,在方少和各位 React.js 大佬的力荐下。大胆的尝试使用 React.js (以前虽然接触过,但没写过)。如果只是实现逻辑,什么框架都可以。写过之后,React.js 的那种掌控感,可实现性,个人很喜欢,虽然也遇到很多坑。说这些,希望能给遇到类似问题的开发者一点参考。

正文

主要记录一下,从不会到折腾出一些东西的过程。写做分享,也写给自己。

:文中涵盖的内容,可能不是有多难,也可能存在一些不正确性。

前期

项目结构

一个适合的项目结构,会给开发带来极大的快感。

> 代表文件夹

    ./src
>assets // 静态资源
>font-awesome
logo.png
>components // 放置 dumb 组件
>alert
>icon // 将 dumb 组件需要的 icon 放到一起,方便管理
alert.js
alert.less // css 模块化,只针对 alert.js 不会造成命名污染
...
>containers // 放置 smart 组件
>http
>api // 针对不同模块的 api ,进行编写,方便多人开发,互不干扰
http.js // 对 axios 的统一配置
>utils
skin.js // 皮肤文件
App.js
index.js
registerServiceWork.js
router.js // 将路由抽离,方便管理和修改
config-overrides.js // webpack 配置
package.json

package.json


...
"dependencies": {
"ajv": "^6.5.2",
"axios": "^0.18.0",
"base64-js": "^1.3.0",
"crypto-js": "^3.1.9-1",
"file-loader": "^1.1.11",
"prop-types": "^15.6.2",
"react": "^16.4.2",
"react-app-rewire-less": "^2.1.2",
"react-dom": "^16.4.2",
"react-modal": "^3.5.1",
"react-router-dom": "^4.3.1",
"react-scripts": "1.1.4",
"react-app-rewired": "^1.5.2"
},
...
  • 为什么没有 redux react-redux ? 放在后面聊,其中也是有些取舍,有些爱恨情仇。
  • 代码检测?当然 eslint 个人习惯用这个

webpack 配置

项目开始,使用 create-react-app,让人意想不到的是,项目开始的困难,并不是来自 React.js 的编写,而是 webpack 4.0create-react-app 中的配置,是隐藏起来的。npm eject 命令可以把配置暴露出来,进行配置。最后发现 react-app-rewired ,让我比较优雅的完成了配置。

react-app-rewired 的配置全都写在 config-overrides.js 中,放在项目根下,与 ./src 同级,下面是我的配置,及部分解释。

config-overrides.js

const path = require('path')
const rewireLess = require('react-app-rewire-less'); /**
* @author Itroad
* @version 0.1.0
*
* Cover webpack's configure
* @param { object } config webpack export.module = {...}
* @param { string } env production || development
* @return { object } custom config
*/
module.exports = function override(config, env) {
if (env === "production") { // File path of build
// 解决 打包后 文件引用路径问题
// 也可以在 package.json homepage: '.',配置 但我喜欢放在一起,方便管理
config.output.publicPath = '.' + config.output.publicPath // For require source file outside of src/. ( remove ModuleScopePlugin )
// config.resolve.plugins = [] // For css module
config.module.rules[1].oneOf[2].loader[2].options['modules'] = true
config.module.rules[1].oneOf[2].loader[2].options['localIdentName'] = '[name]_[local]__[hash:base64:5]' // 配置css 内对于 font 字体文件的引用路径
config.module.rules[1].oneOf[3].options['publicPath'] = '../../' // Path alias
config.resolve.alias = Object.assign({}, config.resolve.alias, {
"@src": path.resolve("src/"),
"@http": path.resolve("src/http"),
"@assets": path.resolve("src/assets"),
"@components": path.resolve("src/components"),
"@containers": path.resolve("src/containers"),
"@reducers": path.resolve("src/reducers"),
"@styles": path.resolve("src/styles"),
"@utils": path.resolve("src/utils"),
"@static": path.join(process.cwd(), './static') // 引用./src 外部资源,默认只在./src 内,本文并未使用,这里只做类举,不做推荐
}) } else { // For require source file outside of src/. ( remove ModuleScopePlugin )
// config.resolve.plugins = [] // For css module
config.module.rules[1].oneOf[2].use[1].options['modules'] = true
config.module.rules[1].oneOf[2].use[1].options['localIdentName'] = '[name]_[local]__[hash:base64:5]'
// config.module.rules[1].oneOf[2].exclude = [
// path.resolve(__dirname, 'node_modules'),
// path.resolve(__dirname, 'src/components'),
// ]
config.module.rules[1].oneOf.push({
test: /\.css$/,
use: ['style-loader', 'css-loader'],
include: [
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, 'src/components'),
]
}) // Path alias
config.resolve.alias = Object.assign({}, config.resolve.alias, {
"@src": path.resolve("src/"),
"@http": path.resolve("src/http"),
"@assets": path.resolve("src/assets"),
"@components": path.resolve("src/components"),
"@containers": path.resolve("src/containers"),
"@reducers": path.resolve("src/reducers"),
"@styles": path.resolve("src/styles"),
"@utils": path.resolve("src/utils"),
"@static": path.join(process.cwd(), './static')
})
} // To support less
config = rewireLess(config, env); return config;
}
  • 对打包后,index.html 中的文件路径,以及 .css 文件中对外部资源引用的路径
  • css 模块化,这样命名就不是头疼(之后会具体说明)
  • 路径别名,避免路径写错,看着也优雅。缺点就是,vs code 的路径自动匹配不能用了

中期

项目结构,配置都搞定了。开始进入代码的编写。

毕竟这不是教程,那就说一些,我认为有点价值的。

由于没有采用任何的 UI 框架,所以都要自己实现

Modal

import React from 'react'
import ReactDom from 'react-dom'
import font from '@assets/font-awesome/css/font-awesome.min.css'
import style from './modal.less' const createModal = (Component, imgSrc, ModalStyle) => {
let body = document.body;
let showDom = document.createElement("div");
// 设置基本属性
showDom.classList.add(style.toast) body.appendChild(showDom); // 自我删除的方法
let close = () => {
ReactDom.unmountComponentAtNode(showDom);
body.removeChild(showDom);
} if(!ModalStyle) {
ModalStyle = {
width: '400px',
height: '500px'
}
}
if(ModalStyle) {
if(parseInt(ModalStyle.width, 10)) {
ModalStyle.width = parseInt(ModalStyle.width, 10) > (window.innerWidth - 100) ? (window.innerWidth - 100) : ModalStyle.width
} else {
ModalStyle.width = '400px'
console.error('createToast width 属性值输入错误, 已使用默认值')
} if(parseInt(ModalStyle.height, 10)) {
ModalStyle.height = parseInt(ModalStyle.height, 10) > (window.innerHeight - 100) ? (window.innerHeight - 100) : ModalStyle.height
} else {
ModalStyle.height = '500px'
console.error('createToast height 属性值输入错误, 已使用默认值')
}
} ReactDom.render(
<div className='ReactModal__Content' style={ModalStyle}>
<div className={style.head + ' skin-modal-head'}>
<div>
<img src={imgSrc} alt="emp"/>
<p>弹框标题</p>
</div>
<i className={font['fa'] + ' ' + font['fa-close']} onClick={close}></i>
</div>
<Component close={close}/>
</div>,
showDom
);
} export default createModal
  • 也许这是函数式编程吧
  • 自己创建节点,灵活植入,用完删除
  • 尺寸做了默认的设置,和根据网页可视范围宽高,对超出范围进行的处理
  • 通过 props 将弹窗关闭方法传入子组件
  • 还有一点,说不上多好的告警日志提示
  • 官网也有 createPortal() 可供使用,我是写完之后才知道,也就不改了
  • parseInt(value, 10) 防止 '' ' ' 和 不可转为数字的值

Alert

类似的方法,实现了 Alert ,其中包括:createConfirm createInfo createWarning createErrorclearAlert 清除 Alert 的方法。说下 createConfirm 其他的没什么。

const createConfirm = (msg, cb) => {
showDom = document.createElement("div"); // 设置基本属性
showDom.classList.add(style.toast)
document.body.appendChild(showDom); // 自我删除的方法
let close = () => {
ReactDom.unmountComponentAtNode(showDom);
document.body.removeChild(showDom);
} const ModalStyle = {
width: '300px',
height: '165px'
} ReactDom.render(
<div className='ReactModal__Content' style={ModalStyle}>
<div className={style.head + ' skin-modal-head'}>
<div>
<img src={confirm} alt="emp"/>
<p>确认</p>
</div>
<i className={font['fa'] + ' ' + font['fa-close']} onClick={close}></i>
</div>
<div className={style.confirmContent}>
<p className={style.msg}>
{msg}
</p>
</div>
<div className={style.footer}>
<div onClick={close}>取消</div>
<div onClick={cb}>确定</div>
</div>
</div>,
showDom
);
}

调用示例


/**
* confirm 点击确认按钮的回调
* @param {any} params
*/
confirmCallBack (params) {
console.log('test', params)
clearAlert()
} /**
* 测试 confirm 弹窗
*/
showNewConfirm () {
createConfirm('确定要删除xxx ?', this.confirmCallBack.bind(this, 123))
}
  • 用了一个 cb 回调,来处理 confirm 点击确定的事件

Toast

这个就更简单了

import React from 'react'
import ReactDom from 'react-dom'
import style from './toast.less' const createToast = (text, time) => {
let body = document.body;
let showDom = document.createElement("div");
// 设置基本属性
showDom.classList.add(style.toast) body.appendChild(showDom); // 自我删除的方法
let close = () => {
ReactDom.unmountComponentAtNode(showDom);
body.removeChild(showDom);
}
if(!parseInt(time, 10)) {
time = 1500
}
setTimeout(close, time)
ReactDom.render(
<div className={style.content}>
{text}
</div>,
showDom
);
} export default createToast
  • 第二个参数,传入关闭时间,默认 1500 毫秒

皮肤

这个就是,将皮肤样式,添加在页面的 <style></style>

/**
* @author Jiang yang
*
* @description 生成皮肤样式
* @version 0.0.1
*/ const skin = {} skin.iceBlue = {
// 全局字体颜色
appColor: '#FFFFFF',
appBgColor: 'black', // header
headerBgColor: '#010a1c', // 框架头部背景色 // left menu
leftMenuBgColor: '#2c3e50', // 左侧菜单背景色
leftMenuBorderColor: '#2c3e50', // 左侧菜单边颜色 // right menu
rightMenuBgColor: '#2c3e50', // 右侧菜单背景色
rightMenuBorderColor: '#2c3e50', // 右侧菜单边颜色 // content
contentBgColor: 'rgb(60, 71, 84)', // 框架内容部分背景色 // footer
footerBgColor: '#2c3e50', // 框架底部背景色
footerShadowColor: 'black', // 框架底部阴影色 // modal
modalOverlay: 'rgba(49, 52, 70, 0.75)', // 弹窗遮罩层
modalContentBg: '#1f2c3a', // 弹窗背景
modalContentShadow: 'gray', // 弹窗阴影
modalContentTxt: 'white', // 弹窗字体颜色
modalHeadBg: '#091323' // 弹窗头部
} skin.lightBlue = {
// 全局字体颜色
appColor: 'black',
appBgColor: 'white', // header
headerBgColor: 'blue', // footer
footerBgColor: 'blue',
footerShadowColor: 'black', // left menu
leftMenuBgColor: 'white',
leftMenuBorderColor: '#2c3e50', // right menu
rightMenuBgColor: 'white',
rightMenuBorderColor: '#2c3e50', // content
contentBgColor: 'white',
} let getSkinStyle = (skin) => {
if(!skin) {
return '';
}
return `
.skin-app {
color: ${skin.appColor};
background-color: ${skin.appBgColor};
}
.skin-header {
background-color: ${skin.headerBgColor};
}
.skin-left-menu {
background-color: ${skin.leftMenuBgColor};
border-right: 1px solid ${skin.leftMenuBorderColor};
}
.skin-right-menu {
background-color: ${skin.rightMenuBgColor};
border-left: 1px solid ${skin.rightMenuBorderColor};
}
.skin-content {
background-color: ${skin.contentBgColor};
}
.skin-footer {
background-color: ${skin.footerBgColor};
box-shadow: 0 -1px 10px ${skin.footerShadowColor};
}
.ReactModal__Overlay {
background-color: ${skin.modalOverlay} !important;
}
.ReactModal__Content {
background-color: ${skin.modalContentBg} !important;
box-shadow: 0px 0px 10px ${skin.modalContentShadow};
color: ${skin.modalContentTxt};
}
.skin-modal-head {
background-color: ${skin.modalHeadBg};
}
`
} let setSkinStyle = (skin) => {
let styleText = getSkinStyle(skin);
let oldStyle = document.getElementById('skin');
const style = document.createElement('style');
style.id = 'skin';
style.type = 'text/css';
style.innerHTML = styleText;
oldStyle ? document.head.replaceChild(style, oldStyle) : document.head.appendChild(style);
} setSkinStyle(skin.iceBlue) export {skin, setSkinStyle}

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Route } from 'react-router-dom'
import App from './App';
import Login from './components/login/login.js'
import registerServiceWorker from './registerServiceWorker'; import '@utils/skin' // 这里引入即可 ReactDOM.render(
<HashRouter>
<div style={{height:'100%'}}>
<Route exact path='/' component={Login} />
<Route path='/home' component={App} />
</div>
</HashRouter>,
document.getElementById('root')
);
registerServiceWorker();
  • 定义皮肤样色对象,然后根据对象生成样式,然后插入到页面中
  • 多个皮肤,就多个皮肤颜色对象

Menu

其实这本来也不是什么有价值的东西。但是用 React 实现的,对于没做过类似的,算是个参考吧。

menu.js

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'
import font from '@assets/font-awesome/css/font-awesome.min.css'
import style from './menu.less' class Menu extends Component {
static propTypes = {
data: PropTypes.array
} constructor () {
super()
this.state = {
isShow: null
} } /**
* 生命周期钩子: 根据 props 的变化,更新 state
* @param {object} nextProps
*/
static getDerivedStateFromProps (nextProps, prevState) {
if (prevState.isShow) {
return null
} const data = nextProps.data /**
* 递归生成 isShow 对象,控制菜单的展开和收缩
* @param {array | object} data
* @return {item+id: true, item2: false, ...}
*/
function getIsShow (data) {
let isShow = {}
function getIsShowState (data) {
if(data instanceof Array) {
for(let item of data){
getIsShowState(item)
}
} else {
isShow['item' + data.id] = data.show
getIsShowState(data.children)
}
}
getIsShowState(data)
return isShow
} return {
isShow: data ? getIsShow(data) : null
} } /**
* 通过 id 来查找 this.state.isShow 中的数据,从而控制菜单的显示状态
* @param {number} id 菜单 id
*/
handleClickMenu (id) {
let current = {
['item'+ id]: !this.state.isShow['item'+ id]
}
let copyState = this.state.isShow
this.setState({
isShow: Object.assign({}, copyState, current)
})
} handleDisposeOperate (value) {
if(this.props.operateCallBack) {
this.props.operateCallBack(value)
}
} /**
* 递归生成菜单的 DOM 结构
* @param {array} data 菜单数据
* @param {number} id 菜单id
*/
handleCreateMenu (data, id) {
let menuDom = []; if (data instanceof Array) {
let list = []
for (let item of data) {
list.push(this.handleCreateMenu(item))
}
menuDom.push(
<ul key={id ? 'ul-' + id : 'root'} className={style.menuUl} style={{display: id ? this.state.isShow['item' + id] ? 'block' : 'none' : 'block'}}>
{list}
</ul>
)
} else {
let levelClass = data.level === 1 ? 'levelTop' : 'levelItem'
let margLeft = (data.level * 16) + 'px' menuDom.push(
<li key={data.id} id={data.id}>
{
data.children.length > 0
? <div onClick={this.handleClickMenu.bind(this, data.id)} className={style[levelClass]} style={{'paddingLeft': margLeft}}>
<div>
{
data.level === 1
? <img className={style.icon} src={require('./icon/' + data.icon)} alt="icon"/>
: ''
}
<span>{data.name}</span>
</div>
{
this.state.isShow['item' + data.id]
? <i className={font['fa'] + ' ' + font['fa-angle-down']}></i>
: <i className={font['fa'] + ' ' + font['fa-angle-right']}></i>
}
</div>
:
data.operate
? <div onClick={this.handleDisposeOperate.bind(this, data.operate)}>
<div className={style[levelClass]} style={{'paddingLeft': margLeft}}>
<div>
{
data.level === 1
? <img className={style.icon} src={require('./icon/' + data.icon)} alt="icon"/>
: ''
}
<span>{data.name}</span>
</div>
</div>
</div>
: <Link to={data.path}>
<div className={style[levelClass]} style={{'paddingLeft': margLeft}}>
<div>
{
data.level === 1
? <img className={style.icon} src={require('./icon/' + data.icon)} alt="icon"/>
: ''
}
<span>{data.name}</span>
</div>
</div>
</Link>
} {this.handleCreateMenu(data.children, data.id)} </li>
)
} return menuDom;
} render () {
return (
<div className=''>
{this.props.data ? this.handleCreateMenu(this.props.data) : ''}
</div>
)
}
} export default Menu
  • handleCreateMenu() 根据数据,生成菜单结构
  • 关于这种结构,官网也提及,要有 key 这就是为什么传入了 id,同时也是为了控制菜单折叠
  • 菜单的折叠,用 state 来控制,具体看 getIsShow()
  • 这里面我用了些 三元判断 甚至还嵌套了下

部分生命周期将要废弃

虽说这个要看官网,但是在看 React 小书 的时候,也因为生命周期的问题,耽搁了一下。

  • 首先提供一下官网说明,中文的
  • componentWillMount() 这个就不用了,挂载之前的逻辑,写到 constructor()
  • componentWillUpdate() componentWillReceiveProps() 这两个不用了,写到 static getDerivedStateFromProps()

后期

发现前面该开发的,都写差不多。后期也没什么了。

  • 我写了两份文档,DOCS.mdSTANDARD.md

    DOCS.md 是关于相关插件,函数的使用和说明

    STANDARD.md 是这个项目的代码规范文档,部分针对这个项目,多数都是普遍的规范

  • 要求打包之后,还要灵活的配置 接口公共路径

    public 中添加一个文件,env.js ,然后在 index.html 中手动引入

    env.js

    window.PROJECT_CONFIG = {
    production: "http://..."
    }

    就是把变量放到 window 下,这样代码里就能 window.PROJECT_CONFIG.production 取到想要的数据,打包后,这个文件也一样存在,可以更改。环境变更的时候,就不用改代码,然后再打包了。

为何没有使用 Redux ?

这是部分页面



图中两个组件,是有频繁交互的。当时是考虑用 reduxreact-redux 来写。而我也实现了。 但是后来思前想后,觉得这种方式,如果让组里其他前端开发的话,不是那么友好。会写很多 map...

于是,我把右侧菜单,嵌套进了中间的组件中,外面看着没变化。但通信方式,已经变成了父子组件之间的通信方式。这就比较简单了。

这种处理方式,我认为是,用结构,来取代复杂的逻辑。

是的,每个页面,如何需要,都要带有自己的右侧菜单,仁者见仁智者见智吧。

特别感谢

这个一定要说在前面,在学习 React.js 的开始,就有小伙伴推荐了 react 小书, 作者胡子大哈。这本书称得上是

良心巨制 。(截至目前,看了四遍)

React.js 三周 -- 入门到搭建团队基础项目的更多相关文章

  1. 谈谈 React.js 的核心入门知识

    近来React.js变得越来越流行,本文就来谈一谈React.js的入门实践,通过分析一些常用的概念,以及提供一些入门 的最佳编程编程方式,仅供参考. 首先需要搞懂的是,React并不是一个框架,Re ...

  2. React.js 官网入门教程 分离文件 操作无法正常显示HelloWord

    对着React官网的教程练习操作,在做到分离文件练习时,按照官网步骤来却怎么也无法正常显示HelloWord. 经测试,html文件中内容改为: <!DOCTYPE html><ht ...

  3. React第三次入门

    传统HTML开发在处理越来越多的服务器数据和用户交互数据反应到复杂界面的时候,代码量越来越大,难以维护. Angular是基于MVVM的开发框架,重量级..不适用于移动端的web栈, 其UI组件的封装 ...

  4. Node.js 蚕食计划(五)—— Koa 基础项目搭建

    Koa 是由 Express 原班人马打造的超轻量服务端框架 与 Express 相比,除了自由度更高,可以自行引入中间件之外,更重要的是使用了 ES6 + async,从而避免了回调地狱 不过也是因 ...

  5. 第三周博客之一---Oracle基础知识

    一.数据库的定义.作用介绍 1.定义:按照数据结构来组织.存储和管理数据的建立在计算机存储设备上的仓库. 2.数据库的发展历史: 2.1.在1962年数据库一词出现在系统研发的公司的技术备忘录中 2. ...

  6. 大二暑假第三周总结--开始学习Hadoop基础(二)

    简单学习NoSQL数据库理论知识 NoSQL数据库具有以下几个特点: 1.灵活的可扩展性(支持在多个节点上进行水平扩张) 2.灵活的数据模型(与关系数据库中严格的关系模型相反,显得较为松散) 3.与与 ...

  7. maven初始搭建一个基础项目(spring mvc+spring+jdbc mysql+jstl)

    技术选型: 一.项目搭建: 1)创建maven项目 (我博客里面有介绍) 选择aptach的maven-archetype-webapp 填入groupIDhe artifactId等 确认项目名称 ...

  8. springboot2.0入门(二)-- 基础项目构建+插件的使用

    一.idea中新建第一个HelloWorld项目 点击next: 下一步 在这里可以选择我们需要依赖的第三方软件类库,包括spring-boot-web,mysql驱动,mybatis等.我们这里暂时 ...

  9. Spring Boot 项目学习 (三) Spring Boot + Redis 搭建

    0 引言 本文主要介绍 Spring Boot 中 Redis 的配置和基本使用. 1 配置 Redis 1. 修改pom.xml,添加Redis依赖 <!-- Spring Boot Redi ...

随机推荐

  1. ReactiveX 学习笔记(21)使用 Rx.NET + ReactiveUI 进行 GUI 编程

    课题 程序界面由3个文本编辑框和1个文本标签组成. 要求文本标签实时显示3个文本编辑框所输入的数字之和. 文本编辑框输入的不是合法数字时,将其值视为0. 3个文本编辑框的初值分别为1,2,3. 创建工 ...

  2. SQL Server error

    原因:文件没有权限 出错: TITLE: Microsoft SQL Server Management Studio------------------------------ Attach dat ...

  3. idea2017启动ssm项目卡在build阶段后报outofmemory

    如上图,设置build process heap size(Mbytes)(构建过程堆大小(单位MB))为4000,即约4GB.之前设置的是700,修改之后问题解决. 补充:导入新项目后,此参数会初始 ...

  4. 通过启动函数定位main()函数

      如下,通过vc6.0编写一个hello world程序.尝试结合汇编代码.分析启动函数找到main函数.   在printf(xxx)插入断点,调试执行.如下,在堆栈窗口中可见main()下的一个 ...

  5. Python练习-高阶函数-2018.12.03

    1.函数式编程的概念 在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言. 而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远 ...

  6. python问题:AttributeError: 'module' object has no attribute 'SSL_ST_INIT'(转)

    原文地址:http://www.cnblogs.com/zhaijiahui/p/7344778.html AttributeError: 'module' object has no attribu ...

  7. centos 安装解压工作

    解压工具: yum install ark 编辑器: yum install gedit

  8. SPARK安装一:Windows下VirtualBox安装CentOS

    一.虚拟机安装 重点是网络设置,参见:https://www.linuxidc.com/Linux/2018-04/151924.htm 本文用三台2核4g虚拟机做集群,虚拟机安装centos7,如下 ...

  9. activeMq-2 高可用以及集群搭建

    Activemq 的集群方法可以有多种实现方式,我们这里使用zookeeper来实现 要搭建集群,请确保已经搭建好zookeeper环境.这里不再演示. 基本原理: 使用ZooKeeper(集群)注册 ...

  10. js--sort()排序方法的使用--(笔记)

    情况1: var arr = [ 'c', 'd', 'a', 'e' ];      //都是字母的情况arr.sort();//alert( arr );                     ...