Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

怎么样使用lodash,根据分组相同的条件,将多个数据合并分组

$
0
0

怎么使用lodash,根据subGroupgroupcode相同的条件,将下面格式的数据(图1),合并成为图2的数据

图一

  [	
	  {
		  "code": "010101",
		  "name": "",
		  "subGroup":{
			  "code": "0101",
			  "name": "",
			  "group":{
				  "code": "01",
				  "name": ""
			  }
		  }
  
	  },
	  {
		  "code": "010102",
		  "name": "",
		  "subGroup":{
			  "code": "0101",
			  "name": "",
			  "group":{
				  "code": "01",
				  "name": ""
			  }
		  }
  
	  }
  ]

图二

[{
	"group": {
		"code":"",
		"name": "01",
		"subGroup": {
			"code": "0101",
			"name": "",
			"permission": [{
				"code": "010101",
				"name": "",
			},{
				"code": "010102",
				"name": ""
			}]
		}
	}
}]

分布式部署的情况下,如何处理上传的图片/文件?

$
0
0

还是图片/文件上传不和业务代码一起,单独做成图库,放在一个机器?

其中可能涉及一些问题

  • 图片/文件存哪里?

    • 如果以二进制存分布式数据库,二进制很太,平增负担
    • 如果存进文件系统,那么ok,存在哪个机器上?
  • 图片/文件从哪里读取?

求有经验的大牛分享下

我们为什么要做接口管理平台 YApi

$
0
0

前言

随着 Web 技术的发展,前后端分离构架变的越来越流行。前后端分离使后端专注于数据处理和定义前端所需要的接口,前端负责数据的展现和交互,大大细化了开发者的职责,提高了开发效率,但与此同时也带来了一些问题:

  • 对于前端工程师,后端提供的接口文档,大多是不规范的,有使用 wiki 的,有 word 文档的,甚至还有用即时聊天软件沟通的,后端接口对于前端就像一个黑盒子,经常遇到问题是接口因未知原因增加参数了,参数名变了,参数被删除了。对于后端工程师,接口对接时总是需要写冗杂繁琐的文档,需要大量时间去维护接口文档。
  • 前端开发的功能在后端功能还没完成前,因为前端的功能依赖于后端的数据,导致工作无法顺利展开。为了解决这个问题,有些前端工程师在代码注入 json,还有后端工程师临时搭建一套测试数据服务器,这种情况下势必会影响工作效率和代码质量,也不能及时进行字段的更新。
  • 接口数据正确性无法得到保证。前端调用后端的接口数据渲染到 视图,数据一旦出错,将会导致视图和交互也出现问题,保证后端接口数据正确性变的愈来愈重要。接口自动化测试就是用来解决这个问题,但传统的接口测试框架使用成本很高,很多团队采用肉眼比对方式,效率很低。

相关产品调研

我们迫切希望有一款产品能够满足我们的诉求,于是开始寻找市面上类似产品,经过一段时间的分析,最终我们找到了几个比较有代表性的产品 Rap,Nei,Easy-Mock。同时我们按照自己的诉求列出了一些关键的特征:

image.png

Nei 是网易前端事业部的产品,在这些产品中算是做得比较好的, nei 是专注做 saas 服务这块,没有开源版本。对于去哪儿内部,肯定不会把公司机密的接口数据放到第三方平台。

Rap 是阿里妈妈 MUX 团队2013年出的一款产品,从时间上看是同类产品中最早的。Rap 是后端工程师基于 java 开发的,如果想定制部分功能,还需要学习 java,而我们部门大家对 java 都不熟悉。另一方面 Rap 没有接口测试功能,而后端使用其他工具(postman, restlet)测试接口,将导致不能及时更新接口文档。

Easy-mock 是大搜车无线团队出的一款产品,Easy-mock 定位是接口数据的模拟,解决前端依赖后端接口数据的问题,在同类产品中 mock 服务做得比较好。Easy-mock 专注于前端数据的模拟,但无法解决去哪儿现有的问题。

Nei,Rap 接口管理平台共同存在的问题是不易维护接口返回数据。笔者曾跟一个使用过 Rap 的后端工程师聊过,他说每次定义后端接口返回数据字段,好几个百个字段需要更新很长时间。Nei,Rap 是基于维护一个 json-schema 方式定义后端返回数据结构,我们假设某个接口有100个字段,如果基于 json-shema 那么就要维护差不多 600 多左右字段的更新。这么大工作量的,很可能导致后端工程师根本没有动力去维护。

比较遗憾的是,这几款优秀的产品,都缺失了一些我们在意的关键特征。我们可能需要做比较大的改动才能够基本满足自己的需求,这个工作量很有可能会超过重新开发一次。所以我们开始自主研发一个全新的接口管理平台,我们希望它能够提供接口文档管理,接口数据模拟(Mock),接口调试,自动化测试等功能,让前后端接口相关的工作进行的更加高效。这就是 YApi 接口管理平台斐然由来,下面简要聊聊 YApi 是如何实现上述这些特征的。

YApi 解决方案

1. 共同维护一份接口定义,连接前后端

大家看下图,在后端开发接口过程中,接口开发和测试接口这是必不可少的环节,但文档因为没有跟接口开发和测试联系到一起,被孤立。后端要维护对于他们冗杂繁琐的文档,是件收益很低的事情。没有人喜欢做收益低的事情,所以最终的解决办法就是要提高收益。下面详细说明解决方案。

在接口开发过程中,后端通常都会使用 postman 等类似的工具测试接口,而测试接口是在开发过程中一个必要的过程。假如参数有改动,大家肯定会在 postman 等工具上更新字段和测试接口。由此可以联想到, 如果能有一款工具既可用来做测试接口,又能作为接口文档工具,将接口文档和接口测试连接到一起,不就解决了此问题。YApi 解决方案是将接口文档和测试通过单一数据源连接到一起,如果有改动,因为改的是单一的数据源,就不会出现更新滞后和不及时问题。

2. 前端 Mock Server 方案

数据 Mock 服务在开发前期是非常头疼的一个问题。大多数情况下,接口请求参数和返回数据都是后端规定的,在后端接口没有完成之前,接口对于前端就是一个黑洞,可能最初对接口的定义跟实际后端做出的接口会有非常大的不同。这个时候就需要有一个工具,不仅能模拟真实接口的情况,还能关联接口文档,在后端开发过程中,可以随时调整接口定义,并通知给前端开发者改动信息。

在 YApi 平台,前后端只要维护接口定义的响应数据,就可以生成需要的模拟数据,下面这段代码定义了生成数据模板:

{
    "errcode": 0,
    "errmsg": "@string",
    "data": {
        "type":"@pick(1,2,3)",
        "list|1-10": [{
            "uid": "@id",
            "username": "@name"
        }]
    }
}

可生成如下的模拟数据:

{
  "errcode": 0,
  "errmsg": "^*!SF)R",
  "data": {
    "type": 2,
    "list": [
      {
        "uid": "370000200707276255",
        "username": "Ruth Clark"
      },
      {
        "uid": "650000200211185728",
        "username": "Anthony Martin"
      },
      {
        "uid": "370000199201143855",
        "username": "Laura Rodriguez"
      },
      {
        "uid": "610000198704072775",
        "username": "Anthony Perez"
      }
    ]
  }
}

以往的数据 mock 方案难免会影响项目源码,yapi 使用了服务器代理的方案,只需要在你的开发机做下服务器反向代理配置,不用修改项目一行源代码,即可获取到所有的 mock 数据。

基础的 Mock 工具已经能满足大部分的需求了,但有些复杂场景是无法实现的。例如:当我做一个数据列表页面,需要测试某个字段在各种长度下的 ui 表现,还有当数据为空时的 ui 表现。YApi 提供了期望和自定义脚本的功能。 本文主要介绍自定义脚本功能,期望功能可参考 yapi 平台文档。

自定义脚本可根据请求的参数,cookie 信息,使用 js 脚本自定义返回的数据。我们假设有个场景,我希望通过 cookie “_type” 控制列表页面数据显示,假设 _type 是 error,那么列表显示异常错误信息;假设 _type 是 empty ,列表显示为空。可使用下面代码实现:

if(cookie._type == 'error'){
    mockJson.errcode = 400;
}

if(cookie._type == 'empty'){
    mockJson.data.list = [];
}

3.自动化测试

接口开发完成后,后续的迭代是非常多的,每次对源码的修改,都需要大量的测试才能确保接口是否正确。人工判断肯定是不好的,最好的办法是做成自动化,但自动化测试又是一件成本非常高的事情,需要后端人员和QA人员学习相关的框架,和写大量的代码。YApi 简化了这一个过程,基于一个可视化界面,就算不懂程序开发,只需配置相关的参数和断言语句,就能实现自动化测试,非常的易用。

除了基本的功能外,YApi 还提供了强大的 pre-script 和可视化表达式功能,pre-script 包括请求参数处理脚本和响应数据处理脚本两部分。通过自定义 js 脚本方式改变请求的参数和返回的 response 数据。他的使用场景如下:

  • 接口请求参数需要加密及返回 response 解密
  • 接口请求参数需要添加计算 token

可视化表达主要是为了方便用户生成自动化测试所用到的参数,通过一个树形选择性,快速引用所依赖的参数值。 在所有的需要测试的接口配置完成后,点击开始测试,就会按照指定的顺序依次测试所有接口,测试完成后,可查看测试报告。

4.插件机制

YApi 最强大的一点莫过于他的插件机制,我们去哪儿各个业务线有不同的需求,通过 YApi 预留的钩子,开发不同的插件解决,比如我们现有的 qsso 登录,swagger 数据导入就是通过插件机制实现的,我们团队最近还在跟业务部门讨论使用插件实现压力测试功能等。总得来说,YApi基于插件机制,既满足了产品需求的多样性,又保证了内核足够易用和简洁。

5. 开源和易部署

为了帮助更多开发者和提升大家的工作效率,YApi 不仅开源到 github,还提供了一个 cli 工具方便广大开发者部署。使用 yapi-cli 提供的可视化部署方案,即便你不懂任何 nodejs、mongodb 的知识,也能轻松一键部署。

后记

YApi 已在去哪儿大面积使用,对 200+ 项目接口进行管理,每周有上万次 mock 请求。在开源以后,越来越多的公司和团队使用 YApi, github star 数已经上升到 1.1k了。YApi 在未来还将继续专注于接口管理方面的功能,让 YApi 成为各位开发者的好帮手。

访问地址

Node.js 一键领取饿了么手气最佳红包 开源

$
0
0

github 地址 https://github.com/game-helper/eleme

使用

  • 安装 Node.js 9.x +
  • npm i安装依赖
  • 编辑 src/index.js中的 mobile(要领红包的手机号码) 和 url(饿了么红包链接)
  • npm start自动秒红包

本地运行太麻烦?访问 http://eleme.gamehelper.ga/在线服务

截图

以下为脚本运行结果

切记不要用手机打开分享的红包链接,因为进页面就领取了红包,没法再领最佳红包了

有人用过阿里的alinode来管理node应用么

node怎样向文件*.xlsx中追加内容

$
0
0

在网上搜索了多好,但都都用node-xlsx或者其它模块新建一个文件,不能做到在原来文件中追加内容,有做过的请指点。

React构建个人博客

$
0
0

前言

在学习react的过程中,深深的被react的函数式编程的模式所吸引,一切皆组件,所有的东西都是JavaScript。React框架其实功能很单一,主要负责渲染的功能,但是社区很活跃,衍生出了很多优秀的库和工具。个人觉得,想要做好一个项目,往往需要其他库和工具的配合,例如redux管理数据,react-router管理路由等,掌握基本的webpack配置es6语法,然后想要提高性能,还有配合react的钩子函数和immutable.js,什么时候组件不需要重新渲染,next.js服务端渲染等等… 一直有一个想法就是重构自己的博客,刚好这段时间放假,又刚好学习了react,于是就有了这个项目。

项目地址https://github.com/k-water/react-blog如果觉得不错的话,您可以点右上角 “Star” 支持一下 谢谢! ^_^

技术栈

前端

  • react
  • react-redux
  • react-thunk
  • react-router
  • axios
  • eslint
  • maked
  • highlight.js
  • antd
  • es6/7/8

后台

  • spring boot

此项目采用前后端分离的实现,后台接口基于RESTful规范设计,只提供数据,前端负责路由跳转,权限限制,渲染数据等。PS:由于我是个前端er,所以这里主要讲的是前端。

实现的功能

  • [x] admin增删查改博客
  • [x] 博客标签
  • [x] 博客内容markdown
  • [x] 博客内容页展示目录
  • [x] 返回顶部
  • [x] markdown代码高亮
  • [x] 用户登录注册
  • [x] 用户评论
  • [x] 响应式

TODO

  • [ ] 博客分类
  • [ ] 点击标签搜索相关博客
  • [ ] 优化首页侧边栏
  • [ ] 完善归档
  • [ ] 部署上线

效果预览

首页

内容页

用户登录

用户评论

后台管理

个人总结

markdown渲染

在前端渲染markdown的时候遇到了一点问题,相关的包很多,但是各种包解析的结果都有差异,react周边社区推荐的是react-markdown,使用方法也很简单

import ReactMarkdown from 'react-markdown'

const input = '# This is a header\n\nAnd this is a paragraph'
ReactDOM.render(
    <ReactMarkdown source={input} />,
    document.getElementById('container')
)

但是发现react-markdown对表格的支持不太友好,最后采用了marked,结合highlight.js对代码部分实现高亮

import marked from 'marked'
import hljs from 'highlight.js'
  componentWillMount() {
    marked.setOptions({
      highlight: code => hljs.highlightAuto(code).value
    })
  }

最后解析出来的是一个字符串,还需要将它插入dom中,由于安全问题,React不提倡将字符串直接插入dom中,但React保留了一个API,可以这样做:

<div className="article-detail" 
  dangerouslySetInnerHTML={{ __html: marked(output)) }} />

React组件化

react的组件由dom视图和state组成,state是数据中心,它的状态决定着视图的状态。react只负责UI的渲染,与其他框架监听数据动态改变dom不同,react采用setState来控制视图的更新。setState会自动调用render函数,触发视图的重新渲染,如果仅仅只是state数据的变化而没有调用setState,并不会触发更新。说到组件,就必须了解react组件的生命周期,官方的图解如下:

关于这部分的解释网上有很多,可以自行查阅。而我在开发过程用的最多的就是

  • componentWillMount()
  • componentDidMount()
  • shouldComponentUpdate(nextProps, nextState) 这几个钩子函数了,关于性能优化,可以在shouldComponentUpdate上作文章,由于shouldComponentUpdate默认返回true,简单的方法可以通过比较更新前后的数据结构是否相同来判断组件是否需要重新渲染,这时候就可以采用immutable.js了。

组件之间通信

react是单向数据流,自上而下的传递数据。解决复杂组件之间通信的方法有很多。一般父子组件通信是最简单的,父组件将一个回调函数传递给子组件,子组件通过this.props直接调用该函数与父组件通信。

如果组件之间嵌套很深,可以使用上下文getChildContext来传递信息,这样在不需要将函数一层层往下传,任何一层的子级都可以通过this.context直接访问,react-redux内部实现就是利用此方法。

兄弟组件之间无法直接通信,它们需要利用同一层的上级作为中转站。

Redux

redux不是必须的,如果不是复杂的组件通信,逻辑简单,用context就行。redux并不是react特有的,其他框架也可以使用redux。当初为了学习redux花费了不少时间,一开始并不理解redux中间的操作,看了很多前辈们写的文章才逐渐明白。简单说说redux。 redux由三部分组成:store, reducer, action

store是一个对象,它主要由三个方法: dispatch用于action的分发,当action传入dispatch会立即执行,有些时候我们不想它立刻触发,可以在createStore中使用middleware中间件对dispatch进行改造,例如redux-thunk,不过这是react-radux做的事了。 subscribe顾名思义,监听器,监听state的变化,这个函数在store调用dispatch时会注册一个listener监听state变化。 getState获取store中的state,当我们用action触发reducer改变了state时,需要拿到新的state里面的数据。getState在两个地方会用到,一是通过dispatch提交action后store需要拿到state里面的数据,二是利用subscribe监听到state发生变化后调用它来获取新的state数据。

说了这么多,store的核心代码其实很短:

/**
 * 应用观察者模式
 * @param {Object} state
 * @param {Function} reducer
 */
function createStore(reducer) {
  let state = null
  const listeners = []
  const subscribe = listener => listeners.push(listener)
  const getState = () => state
  const dispatch = action => {
    // 覆盖原对象
    state = reducer(state, action)
    listeners.forEach(listener => listener())
  }
  // 初始化state
  dispatch({})
  return {
    getState,
    dispatch,
    subscribe
  }
}

另一部分,reducer是一个纯函数(pure function),它接收一个state和action作为参数,根据action的type返回一个新的state,如果传入的action type没有匹配到,则返回默认的state,简单实现如下:

function reducer(state, action) {
  if (!state) {
    return {
      title: {
        text: "water make redux",
        color: "red"
      },
      content: {
        text: "water make redux",
        color: "green"
      }
    }
  }
  switch (action.type) {
    case "UPDATE_TITLE_TEXT":
      return {
        ...state,
        title: {
          ...state.title,
          text: action.text
        }
      }
    case "UPDATE_TITLE_COLOR":
      return {
        ...state,
        title: {
          ...state.title,
          color: action.color
        }
      }
    default:
      return state
  }
}

action比较简单,它返回一个对象,其中type属性是必须的,同时也可以传入一些其他的数据。 使用例子如下:

/ 生成store
const store = createStore(reducer)
let oldState = store.getState()
// 监听数据变化重新渲页面
store.subscribe(() => {
  const newState = store.getState()
  renderApp(newState, oldState)
  oldState = newState
})
// 首次渲染页面
renderApp(store.getState())
store.dispatch({
  type: "UPDATE_TITLE_TEXT",
  text: "water is fighting"
})
store.dispatch({
  type: "UPDATE_TITLE_COLOR",
  color: "#f00"
})

React-redux

react-redux则是对redux做了封装,可以在react中直接使用,并且提供了ProviderconnectProvider是一个组件,它接受store作为props,然后通过context往下传,这样react中任何组件都可以通过context获取store。 connect是一个函数,也是一个高阶组件(HOC),通过传入state和dispatch返回一个新的组件,它的写法是如下:

connect(mapStateToProps, mapDispatchToProps, mergeProps, options)(component)

也可以采用装饰器的写法,这需要babel的支持:

@connect(
	state,
	{ func }
)

具体的不多介绍,迷你实现可以看看这个项目:https://github.com/k-water/make-react-redux

最后,star是对我最大的支持~

egg或者koa有数据迁移管理的插件吗

$
0
0

egg或者koa有数据迁移管理的插件吗


关于koa-router的redirect的问题

$
0
0

1.使用重定向功能代码如下

router.redirect("/", '/back/developer/getting-started');
router.all([/\/.*/],async ctx =>{
  await ctx.render('index')
})

我在访问 localhost:5757的时候不会产生重定向的作用 如果写成如下

router.redirect("/front/developer/getting-started", '/back/developer/getting-started');
router.all([/\/.*/],async ctx =>{
  await ctx.render('index')
})

我在front/developer/getting-started的时,刷新页面可以重定向到相应页面, 也就是说"/"不起作用 以下是全部代码部分

const Koa = require('koa')
const app = new Koa()
const debug = require('debug')('koa-weapp-demo')
const bodyParser = require('koa-bodyparser')
const koaStatic = require('koa-static')
const views = require('koa-views');
const proxy = require('koa-proxy');
const config = require('./config');

const job = require("./jobs/syncMd");
job.start();

app.use(koaStatic(__dirname + '/static'));

// 解析请求体
app.use(bodyParser())

// Must be used before any router is used
app.use(views(__dirname + '/views'));


const router = require('./router')

/*
 todo特别迷!重定向又不起作用了,在前端去重定向了
 */
// router.redirect("/", '/back/developer/getting-started');
router.all('/', ctx => {
  ctx.redirect('/back/developer/getting-started');
  ctx.status = 301;
});
router.all([/\/.*/],async ctx =>{
  await ctx.render('index')
})

// 引入路由分发

app.use(router.routes())
  .use(router.allowedMethods())

// 启动程序,监听端口
console.log("listening "+config.port);
app.listen(config.port, () => debug(`listening on port ${config.port}`))

多谢

一键部署Web项目

$
0
0

一般还算完整的Web项目, 数据库,后端,前端三部分必不可少。 数据库需要启动数据库服务。 后端需要启动node服务。 前端需要启动build或者start。 每次的重新开机就意味着这些操作要重来。 写个简单的批处理(.bat)命令就能一键完成。

例子:startup.bat 1.新建文本文档 2.关闭所有命令的回显

@echo off

3.管理员身份

mode con lines=30 cols=60 %1 mshta vbscript:CreateObject(“Shell.Application”).ShellExecute(“cmd.exe”,"/c %~s0 ::","",“runas”,1)(window.close)&&exit cd /d “%~dp0”

4.查看MongoDB服务是否开启

sc query MongoDB|Find “RUNNING”||sc Start MongoDB

5.打开cmd运行parse-server(/k保持命令行执行完不关闭)

start cmd /k “parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test”

6.打开cmd运行parse-dashboard

start cmd /k “parse-dashboard --appId APPLICATION_ID --masterKey MASTER_KEY --serverURL “http://localhost:1337/parse” --appName app --allowInsecureHTTP”

7.打开cmd启动后端服务

start cmd /k “cd /d d:\project\demo\server&&npm run server”

8.打开cmd启动start

start cmd /k “cd /d d:\project\demo\client&&npm run start”

9.保存,修改后缀为.bat 10.双击.bat文件。

如果想开机后就自动运行,找到启动文件夹 C:\Users\zhaoy\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup 将批处理文件复制到该目录下。 重启试试。

简书

pomelo + nginx + wss配置问题请教

$
0
0
有人配置过nginx + wss 没 ?
最近遇到一个难题,跟pomelo有关。 
pomelo有gate, connector两种类型的服务器, 想通过nginx配置,达到访问wss://host/gate , wss://host/connector访问到一下两组服务器

upstream gate{
    gate-server:3014;
}
upstream connector{
   connector-server:3015;
   connector-server:3016;
}
有谁对nginx配置比较熟悉的,还请不吝赐教。 

node 的 orm模块 sequelize实现 MySQL的读写分离时 怎么获取只读的对象来 curd实现操作

$
0
0

development: { database: null, username: null, password: null, host: null, dialect: ‘mysql’, replication: { read: [{ database: ‘xxx_copy’, username: ‘root’, password: null, host: ‘127.0.0.1’, port: 3306, pool: {}, }], write: { database: ‘xxx’, username: ‘root’, password:null, host: ‘127.0.0.1’, port: 3306, pool: {}, } }, pool: { // 重写链接池 max: 20, idle: 30000 }, } // 连接池生成的实例拿的是 主数据库 write的模型对象 // read配置的数据库也连接成功 但是拿不到从数据库 read的实例啊

在 React 工程中利用 Mota 编写面向对象的业务模型

$
0
0

屏幕快照 2018-02-06 上午1.18.57.png

简述

React 是一个「视图层」的 UI 框架,以常见的 MVC 来讲 React 仅是 View,而我们在编写应用时,通常还需要关注更加重要的 model,对于 React 来讲,我们常常需要一个「状态管理」库。然而,目前大多数针对 React 的状态管理库都是「强依赖」过多的侵入本应该独立的业务模型中,导致「业务逻辑」对应的代码并不能轻易在其它地方重用,往往这些框架还具有「强排它性」,但是「业务模型」应该是没有过多依赖,应该是无关框架的,它应该随时可以被用在任何合适的 JavaScript 环境中,使用 mota 你可以用原生的普通的 JavaScript 代码编写你的「业务模型」,并让你的「业务模型」在不同框架、不同运行环境下重用更为容易。

mota 是一个主张「面向对象」的、支持「双向绑定」的 React 应用辅助库,基于 mota 你可以用纯 JavaScript 为应用编写完全面向对象的「业务模型」,并轻易的将「业务模型」关联到 React 应用中。

示例

在线 TodoList 示例 (示例源码)

安装

通过 npm 安装,如下

$ npm i mota --save

或通过 dawn脚手脚加创建工程,如下

$ mkdir your_path
$ cd your_path
$ dn init -t mota
$ dn dev

需要先安装 dawn(Dawn 安装及使用文档

工程结构

一个 mota工程的通常结构如下

.
├── README.md
├── package.json
└── src
    ├── assets
    │   ├── common.less
    │   ├── favicon.ico
    │   └── index.html
    ├── components
    │   ├── todoApp.js
    │   └── todoItem.js
    ├── index.js
    └── models
        ├── TodoItem.js
        ├── TodoList.js
        └── index.js

编写业务模型

在 mota 中「模型」可以是由一个 class或普通的的 Object,整个「业务模型层」会由多个 class和多个 Object组成,而编写模型所需要的知识就是 JavaScript 固有的面向对象编程的知识。

如下示例通过编写一个名为 Userclass创建了一个「用户模型」

export default class User {
  firstName = 'Jack';
  lastName = 'Hou';
  get fullName(){
    reutrn `${this.firstName} ${this.lastName}`;
  }
}

也可以是一个 Object,通常这个模型需要是「单例」时,可采用这种方式,如下

export default {
  firstName: 'Jack',
  lastName: 'Hou',
  get fullName(){
    reutrn `${this.firstName} ${this.lastName}`;
  }
};

在「业务模型」编写完成后,可以通过 @model将某个「类」或「类的实例」关联到指定组件,关联后便可以在组件中使用 this.model访问「模型的成员变量或方法」了,mota 还会自动「收集组件依赖」,在组件「依赖的模型数据」发生变化时,自动响应变化并「驱动组件重新渲染」,如下

import { model,binding } from 'mota';
import React from 'react';
import ReactDOM from 'react-dom';
import User from './models/user';

@model(User)
class App extends React.Component {

  onChange(field,event){
    this.model[field] = event.target.value;
  }

  render(){
    return <div>
      <p>{this.model.fullName}</p>
      <p>
        <input onChange={this.onChange.bind(this,'firstName')}/>
        <br/>
        <input onChange={this.onChange.bind(this,'lastName')}/>
      </p>
    </div>;
  }
}

ReactDOM.render(<App/>, mountNode);

值得注意的是,在使用 @model时如果传入的是一个 class最终每个组件实例都会自动创建一个 独立的实例,这样带来的好处是「当一个页面中有同一个组件的多个实例时,不会相互影响」。

属性映射

在 React 中通常会将应用折分为多个组件重用它们,并在用时传递给它「属性」,mota 提供了将「组件属性」映射到「模型数据」的能力,基于 model编程会让「视图层」更单一,专注于 UI 的呈现,,如下

@model({ value: 'demo' })
@mapping(['value'])
class Demo extends React.Component {
  render () {
    return <div>{this.model.value}</div>;
  }
}

上边的代码通过 mappingDemo这个组件的 value属性映射到了 model.value上,在组件的属性 value发生变化时,会自动同步到 model.value中。

通过一个 map 进行映射,还可以让「组件属性」和「模型的成员」使用不同名称,如下:

@model({ value: 'demo' })
@mapping({ content: 'value' })
class Demo extends React.Component {
  render () {
    return <div>{this.model.value}</div>;
  }
}

上边的代码,将组件 demo 的 content属性映射到了 model.value上。

自执行函数

mota 中提供了一个 autorun函数,可用于装饰 React 组件的成员方法,被装饰的「成员方法」将会在组件挂载后自动执行一次,mota 将「收集方法中依赖的模型数据」,在依赖的模型数据发生变化时会「自动重新执行」对应的组件方法。

示例

import { Component } from 'react';
import { model, autorun } from 'mota';
import DemoModel from './models/demo';

@model(DemoModel)
export default Demo extends Component {

  @autorun
  test() {
    console.log(this.model.name);
  }

}

上边的示例代码中,组件在被挂载后将会自动执行 test方法,同时 mota 会发现方法中依赖了 model.name,那么,在 model.name发生变化时,就会重新执行 test方法。

监听模型变化

mota 中提供了一个 watch函数,可用于装饰 React 组件的成员方法,watch可以指定要观察的「模型数据」,在模型数据发变化时,就会自动执行「被装饰的组件方法」,watch还可以像 autorun一样自动执行一次,但它和 autorun还是不尽相同,主要有如下区别

  • autorun会自动收集依赖,而 watch不会关心组件方法中有何依赖,需要手动指定依赖的模型数据
  • watch默认不会「自动执行」,需显式的指定「立即执行参数为 true」,才会自动执行首次。
  • autorun依赖的是「模型数据」本身,而 watch依赖的是「计算函数」每次的「计算结果」

示例

import { Component } from 'react';
import { model, autorun } from 'mota';
import DemoModel from './models/demo';

@model(DemoModel)
export default Demo extends Component {

  @watch(model=>model.name)
  test() {
    console.log('name 发生了变化');
  }

}

上边的代码,通过 watch装饰了 test方法,并指定了观察的模型数据 model.name,那么每当 model.name发生变化时,都会打印 name 发生了变化.

watch是否重新执行,取决于 watch的作为第一个参数传给它的「计算函数」的计算结果,每当依赖的模型数据发生变化时 watch都会重执行计算函数,当计算结果有变化时,才会执行被装饰的「组件方法」,示例

export default Demo extends Component {

  @watch(model=>model.name+model.age)
  test() {
    console.log('name 发生变化');
  }

}

有时,我们希望 watch能首先自动执行一次,那么可通过向第二个参数传一个 true声明这个 watch要自动执行一次。

export default Demo extends Component {

  @watch(model=>model.name,true)
  test() {
    console.log('name 发生变化');
  }

}

上边的 test方法,将会在「组件挂载之后自动执行」,之后在 model.name发生变化时也将自动重新执行。

数据绑定

基本用法

不要惊诧,就是「双向绑定」。mota主张「面向对象」,同样也不排斥「双向绑定」,使用 mota 能够实现类似 ngvue的绑定效果。还是前边小节中的模型,我们来稍微改动一下组件的代码

import { model,binding } from 'mota';
import React from 'react';
import ReactDOM from 'react-dom';
import User from './models/user';

@model(User)
@binding
class App extends React.Component {
  render(){
    const { fullName, firstName, popup } = this.model;
    return <div>
      <p>{fullName}</p>
      <p>
        <input data-bind="firstName"/>
        <button onClick={popup}> click me </button>
      </p>
    </div>;
  }
}
ReactDOM.render(<App/>, mountNode);

其中的「关键」就是 @binding,使用 @binding后,组件便具备了「双向绑定」的能力,在 jsx中便可以通过名为 data-bind的自定义 attribute进行绑定了,data-bind的值是一个「绑定表达式字符串」,绑定表达式执行的 scopemodel而不是 this,也就是只能与 模型的成员进行绑定。

会有一种情况是当要绑定的数据是一个循环变量时,「绑定表达式」写起会较麻烦也稍显长,比如

@model(userModel)
@binding
class App extends React.Component {
  render(){
    const { userList } = this.model;
    return <ul>
     {userList.map((user,index)=>(
       <li key={user.id}>
         <input type="checkobx" data-bind={`userList[${index}].selected`}>
         {user.name}
       </li>
     ))}
    </ul>;
  }
}

因为「绑定表达式」的执行 scope默认是 this.model,以及「表达式是个字符串」,看一下 userList[${index}].selected这并不友好,为此 mota 还提供了一个名为 data-scopeattribute,通过它能改变要绑定的 scope,参考如下示例

@model(userModel)
@binding
class App extends React.Component {
  render(){
    const { userList } = this.model;
    return <ul>
     {userList.map(user=>(
       <li key={user.id}>
         <input type="checkobx" data-scope={user} data-bind="selected">
         {user.name}
       </li>
     ))}
    </ul>;
  }
}

通过 data-scopeinput的绑定上下文对象声明为当前循环变量 user,这样就可以用 data-bind直接绑定到对应 user的属性上了。

原生表单控件

所有的原生表单控件,比如「普通 input、checkbox、radio、textarea、select」都可以直接进行绑定。其中,「普通 input 和 textrea」比较简单,将一个字符类型的模型数据与控件绑定就行了,而对于「checkbox 和 radio」 有多种不同的绑定形式。

将「checkbox 或 radio」绑定到一个 boolean值,此时会将 checkbox 或 radio 的 checked 属性和模型数据建立绑定,checked 反应了 boolean变量的值,参考如下示例

@model({ selected:false })
@binding
class App extends React.Component {
  render(){
    return <div>
      <input type="checkbox" data-bind="selected"/>
      <input type="radio" data-bind="selected"/>
    </div>;
  }
}

如上示例通过 this.model.selected就能拿到当前 checkbox 或 radio 的选中状态。

将 checkbox 绑定到一个「数组」,通常是多个 checkbox 绑定同一个数组变量上,此时和数据建立绑定的是 checkbox 的 value,数据中会包含当前选中的 checkbox 的 value,如下

@model({ selected:[] })
@binding
class App extends React.Component {
  render(){
    return <div>
      <input type="checkbox" data-bind="selected" value="1"/>
      <input type="checkbox" data-bind="selected" value="2"/>
    </div>;
  }
}

如上示例,通过 this.selected就能知道当前有哪些 checkbox 被选中了,并拿到所有选中的 value

将多个 radio 绑定我到一个「字符类型的变量」,此时和数据建立绑定的是 raido 的 value,因为 radio 是单选的,所以对应的数据是当前选中的 radio 的 value,如下

@model({ selected:'' })
@binding
class App extends React.Component {
  render(){
    return <div>
      <input type="radio" data-bind="selected" value="1"/>
      <input type="radio" data-bind="selected" value="2"/>
    </div>;
  }
}

通过 this.model.selected就能拿到当前选中的 radio 的 value

自定义组件

但是对于一些「组件库」中的「部分表单组件」不能直接绑定,因为 mota 并没有什么依据可以判断这是一个什么组件。所以 mota 提供了一个名为 bindable的函数,用将任意组件包装成「可绑定组件」。

bindable 有两种个参数,用于分别指定「原始组件」和「包装选项」

//可以这样
const MyComponent = bindable(opts, Component);
//也可这样
const MyCompoent = bindable(Component, opts);

关建是 bindable需要的 opts,通过 opts我们可以造诉 mota 如何绑定这个组件,opts中有两个重要的成员,它的结构如下

{
  value: ['value 对应的属性名'],
  event: ['value 改变的事件名']
}

所以,我们可以这样包装一个自定义文本输入框

const MyInput = bindable(Input,{
  value: ['value'],
  event: ['onChange']
});

对这种「value 不需要转换,change 能通过 event 或 event.target.value 拿到值」的组件,通过如上的代码就能完成包装了。

对于有 onChangevalue的这类文本输入组件,因为 opts 的默认值就是

{
  value: ['value'],
  event: ['onChange']
}

所以,可以更简单,这样就行,

const MyInput = bindable(Input);

而对于 checkbox 和 radio 来讲,如上边讲到的它「根据不同的数据型有不同的绑定形式」,这就需要指定处理函数了,如下

const radioOpts = {
  prop: ['checked', (ctx, props) => {
    const mValue = ctx.getValue();
    if (typeof mValue == 'boolean') {
      return !!mValue;
    } else {
      return mValue == props.value;
    }
  }],
  event: ['onChange', (ctx, event) => {
    const { value, checked } = event.target;
    const mValue = ctx.getValue();
    if (typeof mValue == 'boolean') {
      ctx.setValue(checked);
    } else if (checked) ctx.setValue(value);
  }]
};

通过 prop的第二个值,能指定「属性处理函数」,event 的第二个值能指取「事件处理函数」,处理函数的 ctx是个特殊的对象

  • ctx.getValue能获取「当前绑定的模型数据」
  • ctx.setValue能设置「当前绑定的模型数据」

上边是 radio的配置,首先,在「属性处理函数」中通过绑定的「模型数据的类型」决定 checked最终的状态是什么,并在函数中返回。再次,在「事件处理函数」中通过绑定的「模型数据的类型」决定将什么值回写到模型中。

通过「属性处理函数」和「事件处理函数」几乎就能将任意的自定义组件转换为「可绑定组件」了。

另外,对于常见的 CheckBoxRadio类型的组件 mota 也提供了内建的 opts配置支持,如果一个自定义组件拥有和「原生 checkbox 一致的属性和事件模型」,那边可以直接用简单的方式去包装,如下

const MyCheckBox = bindable('checkbox',CheckBox);
const MyRadio = bindable('radio',Radio);

好了,关于绑定就这些了。

文档

链接

深入理解React源码 - 界面更新 VII(正式版)

$
0
0

汉化完成,https://zhuanlan.zhihu.com/p/33612220

本文也同时发表在我的博客(http://link.zhihu.com/?target=http%3A//holmeshe.me/understanding-react-js-source-code-initial-rendering-VII/)和HACKERNOON(https://hackernoon.com/understanding-the-react-source-code-vii-b08ccb3b3f01)

上次我们聊完了Transaction核心类和它的一个实例ReactDefaultBatchingStrategyTransaction。然而这个Transaction 实例仅仅是一个开始 。

本篇中,我们将探究其它的Transaction 实例。看懂了这些,界面更新逻辑的轮廓就画的出来了。

eggjs在route.js里动态加载从数据库里拿的route数据,这样写有问题吗?

$
0
0

改成了async,能够实现需求,但是不知道这样做有没有什么其他问题?


[ Life-Commit ] - 从今天起,用git记录你的人生吧!

$
0
0

LOGO200.png

github link: https://github.com/ByronHsu/life-commit

你曾否想过,如果你的人生是 git 上唯一的一条 branch,你会如何用一个个 commits 记载在生命中发生的点点滴滴呢? life-commit 是一个 cli 工具,能让你用 terminal 建造属于你自己的 life branch,更支援一键将本端所有 commits 视觉化为静态网页,如果你喜欢我的专案,欢迎给个 star!

这是我用node.js写的第一个 npm package,所以很多地方都还不甚完善,如果有任何建议或指教,欢迎直接发 PR 和 issues! 谢谢大家!

Credits: Inspiration: https://github.com/carloscuesta/gitmoji-cli UI: https://codepen.io/itbruno/pen/KwarLp LOGO: https://www.facebook.com/gary8621

async/await 相关问题请指教

$
0
0
export default class DimensionApi extends BaseService {
    query(params) {
        return super.query(url, params)
    }

    async query1(params) {

        var a = await Api.query(url, params)
        console.log('query', a)  // 这里是一个axios对象
        return a

    }
}
// 调用
a = new DimensionApi('url').query1()  // 这个a 是个promise

为什么a 还是一个处于pending状态的promise呀? 在类里面的console打出来就已经不是一个promise了呀,我是什么地方用错了?

node做图片批量剪裁

$
0
0

1.node 可不可以做图片批量剪裁 就是浏览器上传多张图片,批量处理成规定宽高的尺寸的图片,不用在一张一张的处理

[北京] 金熙金融公司招聘 Java + Node.js 软件工程师2名

$
0
0

金熙金融信息服务有限公司主要做互联网金融业务,目前属于初创阶段,新人加入机会多。

工作地点

北京市石景山区政达路6号北方中惠国际D座,地铁1号线和公交都比较方便。

职位简介

主要完成技术平台的后端和前端开发和维护工作,具备基础知识即可,公司负责培训。

职位要求

  • 熟悉Java或Node.js
  • 熟悉常用的开源Web开发框架
  • 了解Linux操作系统
  • 一年以上工作经验
  • 学历不限

工作环境

较好

薪资待遇

福利完善,待遇较好,具体根据情况面谈。

联系方式

联系人:川月

微信号:chuanyue2081

发送简历至: hr@jinxi360.com

上传文件时,怎样返回上传进度给前台?

$
0
0

我通过formidable中间件,获取文件上传进度,怎样返回给前台呢?

form.on('progress', function(bytesReceived, bytesExpected) {
            console.log(`bytesReceived:${Math.round(bytesReceived/bytesExpected*100)}`)
        });
Viewing all 14821 articles
Browse latest View live