反馈系统(feedback system)是具备闭环信息通道的系统。前端
定义: 将系统的后果或输出信息采集、处理,而后送回输入端并据此调整系统行为的系统。因为信息流通构成闭合环路, 它也称为闭环系统。 反馈做用经常用于检测信号误差以及对象特性的变化,并以此来控制系统行为以及消除偏差。 它又被称为反馈控制或者按照偏差控制的系统。node
主要能够分为自建的反馈系统和使用第三方工具做为反馈系统:react
邮件形式:webpack
如Tower,咱们在 https://tower.im/help 的底部能够看到下面的界面:es6
进入帮助页面,若是咱们没有找到答案,就能够在底部发现这样的一个反馈系统,邮箱是网站默认填充的, 而后咱们能够在内容框中填写文字、图片等, 开发、客服人员收到邮件以后会经过邮件的方式发送给用户。 web
显然,这种方式是封闭的,只有发问的用户能够收到解决邮件,而其余用户看不到更多用户的问题与回答。 mongodb
几分钟以后,我就接受到了这样的一封反馈邮件,很好地解决了个人问题, 能够看到,Tower的入口也是很是方便地。typescript
对话形式:数据库
叮当网站就是这种对话形式的反馈,在右下角有一个留言的图片,点击这个图片以后就会进入一个对话的页面,这个页面还可使用一个新的iframe来显示,以下所示:npm
这种方式最大的好处就是直观、方便追问、具备及时性, 可是不太方便跟踪员工处理的进度状况。
工单形式:
在阿里云的网站上,咱们能够添加建议并提交,提交以后咱们能够看到显示以下:
能够看到提交以后,就会造成一个工单,实时的反映当前状况, 即 ‘已提交’、‘预审经过’、‘已采纳’、‘已实现’ 几个步骤。 对于仅仅是提交的建议,咱们还能够进行再次编辑,另外,全部的建议其余用户也都是能够看到的,全部人(固然包括管理员)能够进行反馈、评论、投票等功能。
在此网站能够查看:https://connect.aliyun.com/suggestion/5293?spm=5176.8409797.user.1.38c60aa8v2lqt7
需求池形式:
https://www.mockplus.cn/
这是一个作原型的工具,更为高效、简单。
从反馈系统的基本框架来看,此系统在在前端至少须要两个页面(或者说是网站) --- 用户反馈网站 以及 后台管理网站。
第一部分:
即须要在网站的的主页面或者帮助页面处提供一个入口,由此进入反馈界面,在反馈界面中的须要提供一个表单用于提交反馈,内容以下:
输入完成以后提示已提交至后台便可。
另外,在反馈界面,咱们还能够列出一些常见的问题和回答,这样,用户也许就不须要去提问了。
第二部分:
固然,除了一个提交反馈的表单仍是不够的,还须要一个“个人反馈”界面。
即在第一部分中,咱们反馈进行了提交,在“个人反馈”界面就须要展现对于个人全部反馈的详情了。
既然有个人反馈界面,这也就是说每次你进入这个网站的时候仍是须要惟一标识的,那么就须要使用登陆和注册功能,这样才能在你下一次登陆的时候将你的相关的建议展现出来。
后台管理界面会稍微复杂一些。
重要: 能够作成推送平台的js插件!
作成插件以后,咱们就能够在多个产品的主页上使用了。 这种场景每每是用于一家较大的公司,公司旗下有不少的产品,每一个产品可能都会单独建站,在每个网站上,咱们能够添加一个特定产品的js插件,然后台管理页面是保持基本不变的。
登陆、注册、请求反馈界面、请求后台管理界面、添加产品、请求全部产品、请求某个产品下的全部反馈(每条反馈是一个文档,每一个文档中包含了反馈的具体信息,题目,信息,评论(评论须要包括评论人、评论时间等),状态、所属用户等等,键值对不是简单的string,设计的有规律一些便可。), 请求某个用户的全部建议(在某个产品下去查找便可,那么保存用户信息时还须要保存用户所使用到的产品类别,这样在搜索产品时会比较快),
最终的项目架构:
这个项目分红了两个文件来作,一个做为主服务器,另外一个做为辅助服务器,那个辅助服务器在发送请求时是代理到主要的服务器上的。前端采用基本的分层方式,后端采用的是MVC的架构方式。
build是服务器相关文件。 model是数据库的相关文件。 node_modules是存放的一些包。 router是存放的路由文件。 src是react的相关文件(src中components存放一些基本的组件,pages存放整个的页面, redux存放的是项目状态管理的相关文件,index.tsx是react项目的渲染页面)。 www是服务器上存放的静态文件。 最后就是基本的 package.json、settings.js(数据库配置)、tsconfig.json(typescript的相关配置文件)、webpack.config.js(webpack相关文件)。
一、应该创建几个文件,即开启几个服务器(项目)?
第一种方式: 开启一个服务器。
即认为只有一个服务器,在用户点击按钮时,触发一个请求,从后端请求到反馈系统的页面,这样就能够进行简单的操做了。
对于管理员,也能够请求到服务器端的一个管理员的页面。
这样在技术上是能够作到的,可是在前端处理上会出现问题。 好比使用webpack打包的时候,就会把全部的js打包,可是实际上在用户和管理员处所使用的js并非全部的,这样就会形成浪费。
第二种方式: 开启两个服务器
这样的方式最简单、清楚、明了。
结果: 实际上,咱们应该当作两个项目来作,也就是说,须要同时进行两个项目来作,一个项目用于写后天管理系统(较多主要是数据库的处理),另外一个项目主要是写前端反馈系统。
二、 这个任务的实际使用场景是这样的吗?
即管理员的界面始终只有一个,而用户反馈界面的界面是会随着不一样产品的改变而改变的,会有不一样的网站来进行请求。
结果:
三、 使用react、 ant.design能够吗?
结果: 能够尝试使用 ant.design , 由于 ant.design 是蚂蚁的一套前端框架,适合react项目的使用,可能会遇到一些坑,可是仍是能够尝试考虑使用的。
四、 这里所说的插件究竟是什么?怎么去理解? 可配置是说添加一个配置文件、配置对象吗?
插件实际上就是一段js代码,经过这个js代码,咱们能够将这个js插件暴露出来的方法进行 init 而后适用在某个产品上。
五、什么是堆楼模式? 这里所使用的反馈系统是工单形式的吗?
即评论是一层一层的,而不是缩进的形式。 反馈系统能够看做是工单形式的。
六、总体的思路应该是怎样的?
对于后台管理界面是直接使用node做为后台,而后使用react来写前台管理界面,这个是通用的。 另外,这个后台除了提供管理界面以外,也应该提供反馈平台所须要的接口。 另外,还须要一个通用的普通端,这里能够本身选产品的类型等。
七、数据库的设计应该是怎么样的?
在数据库中,咱们须要存储的信息包括管理员信息、用户信息、建议信息、评论信息、产品信息等等, 对于这些信息,咱们应当尽可能采用扁平化的风格进行存储。使用链接的方式。
(1)产品存储
(2)建议存储
(3)评论存储
(4)管理员存储
(5)用户存储
即在数据库中须要创建5个集合,这样咱们就能够进行简单的系统操做了。能够看到,上面的数据库的设计尽可能是扁平化的,而没有进行扎推。
而且咱们应当提升可重用性,不要在不一样的表中创建了太多相同的内容,这是不合适的。
八、 ant.design究竟应该怎么用?
咱们在使用的时候应该尽量多地去考虑其源码,这样,咱们才能更理解,也能有所学习。
没错,用户的权限很小,用户只能提交以后,看到其答案已经提交了,后续的工做就彻底取决于管理员了。
因此说,对于建议的管理,主动权彻底在管理端,这样才会比较好控制数据,不至于混乱,就像redux的单向数据流的方式是同样的。
十、登陆、注册这部分应该怎么作? 管理员、用户、产品、建议的ID怎么设置能够保证没有大的问题? 怎么保证不会重复? 表的设计是否有问题。
一、 对于惟一ID,咱们可使用 uuid (在项目中咱们直接 npm install uuid --dev 便可),这个工具能够帮助咱们快速解决问题,而且ID是不会重复的。
二、 对于登陆、注册的问题,咱们能够把这个反馈的网站看作一个黑盒子,而后只须要对之有肯定的输入, 就能保证其输出,因此,咱们能够将其i做为插件来想, 对于登陆、注册的事情由不一样的网站来解决便可。 而咱们须要作的就是在后台处理好便可。 而在管理端仍是比较容易理解的,就是必须登陆注册才能查看产品等等。
十一、 uuid介绍。
参考文章: https://www.npmjs.com/package/uuid
http://www.jianshu.com/p/d553318498ad
UUID是128位的全局惟一标识符,一般由32字节的字符串表示。它能够保证时间和空间的惟一性,也称为GUID,全称为:UUID ―― Universally Unique IDentifier,Python 中叫 UUID。
十二、 如何实现代理服务器呢?
很简单,咱们打开了两个项目,可是咱们只但愿在一个项目的服务器上处理各类服务器、数据库的数据, 这样,咱们直接将次服务器直接代理到主要的服务器便可。以下所示:
第一步:
npm install http-proxy-middleware --save-dev
第二步:
在dev-server.js中,须要引入 http-proxy-middleware,而后:
var proxyTable = { '/api': { target: 'http://127.0.0.1:8000/', // 本地node服务器 changeOrigin: true, pathRewrite: { '^/api': '/' } } };
上面就是咱们的基本设置,固然,在proxyTable中能够代理多个服务器,但这里咱们只须要一个。 思路就是对api代理,而后发出的时候再重写。
// proxy api requests Object.keys(proxyTable).forEach(function (context) { var options = proxyTable[context] if (typeof options === 'string') { options = { target: options } } app.use(proxyMiddleware(options.filter || context, options)) })
ok! 就是这么简单了,这样就能够完成服务器的代理了。
下面咱们就能够发出一个请求了:
componentWillMount () { fetch("/api/getAllProduct", { method: "GET" }).then(function(res) { console.log('进入'); return res.json(); }).then(function (data) { console.log(data); if (data.code == 200) { console.log('获取到全部产品' ,data.data); } else { console.log(data.message); } }) }
即: 这里咱们在 localhost: 3000 的服务器下发出了指向 localhost:8000 服务器的资源。
1三、屡次看到服务器崩溃! 为何呢?
即如上所示,乍一看彷佛并无什么解决的办法,都是不知道的文件,可是,若是咱们仔细看,仍是能够发现致使问题的地方的, 好比这里咱们能够看到 router 下的 index.js 问题, 接着锁定 product.js 的问题, 再去追究,能够发现,咱们在出错的时候,没有及时关闭mongodb数据库,这样,就会报错,修复了这个问题以后,就能够正常获取数据了。
14、在使用redux的过程当中,遇到了一个问题。 即在一个问题列表页,点击每一条链接以后,能够进入这个列表的详情页,那么如何在详情页获取到数据呢? 目前的方法是这样的, 即在列表页的Link进行路由跳转的时候,将这条列表的suggestionId传递到详情页中去,而后在 componentWillMount() 这个钩子函数中使用下面的方法:
componentWillMount () { const suggestionId = this.props.location.query.suggestionId; this.props.filterSug(suggestionId); console.log(suggestionId); }
即获取到当前的 suggestionId, 而后经过一个 action 筛选 store 中的这条建议。
function handleSuggestions (state = {allSuggestions: [], filteredSug: []}, action) { switch (action.type) { case 'ADD_ALL_SUGGESTIONS': const newSug = Object.assign([], action.data); return { allSuggestions: newSug } case 'FILTER_SUGGESTION': return { allSuggestions: state.allSuggestions, filteredSug: state.allSuggestions.filter(function (item, index) { return item.suggestionId == action.id; }) } default: return state; } }
接着,咱们在再从store中获取这条建议。以下:
function mapDispatchToProps (dispatch) { return { filterSug: (id) => dispatch( filterSuggestion(id) ) } } function mapStateToProps (state) { return { filteredSug: state.handleSuggestions.filteredSug } }
接着,咱们就能够在render函数中使用这个 prop 了,即:
const {filteredSug} = this.props;
可是这样致使的一个问题就是: 经过 filteredSug.map 调用时,发现会报错,即 没法读取 map 所在的值,他是 undefined 的。
而若是咱们使用下面的方法:
const {filteredSug = [] } = this.props;
即便用es6中的默认值的方法,这样就不会报错了,而且能够正常显示这条建议。 这是为何? 我以前的想法是: 由于在 store 中存储时,就会有一个默认值,因此,就算是直接获取应该也是一个空的数组,而不是undefined啊,为何这里还须要提供一个默认值呢?
首先能够肯定的是,在store中的state发生变化的时候,就会及时的经过页面进行最新的渲染,这样页面就会及时的变化,即最开始 filteredSug 是 [], 而后当reducer处理完了 action 以后,就会改变state,这样 filteredSug 就成了有一个对象元素的数组了,这样咱们就能够进行map了。
之因此页面会随着数据发生变化,是由于页面对数据有了一个订阅,来监听变化。getState函数能够获取当前的state。
下面,咱们须要测试的就是在 const {} = this.props;和后面的 mapStateToProps 获取的速度问题(即谁先谁后),测试以下:
在 componentWillMount中添加下面的语句:
console.log('在componentWillMount中的时间', new Date().getTime());
在render函数下添加下面的语句:
const {filteredSug} = this.props; console.log('render函数时间', new Date().getTime());
在 mapStateToProps中添加下面的语句:
function mapStateToProps (state) { console.log('获取store中的state的时间', new Date().getTime(), state); return { filteredSug: state.handleSuggestions.filteredSug } }
而后,咱们开始测试,加载这个页面,结果以下:
这里的总体思路很是简单,就是首先进入页面,而后第一步就获取到当前的state, 这样的好处在于,第一步获取到以后就能够在后面的各个钩子函数、render函数中使用了,可是咱们能够发现一个问题,就是在handleSuggestions这个reducer里只有allSuggestions可是并无filteredSug。 第二步进入了componentWillMount钩子函数中,这样就能够直接出发filter咱们想要的suggestion的action了。 第三步就是开始render。 因为在第一步的过程当中就没有获取到filterSug,因此在render的时候,就能够发现map的是一个undefined值。 第四步就比较有意思了,就是在咱们以前触发了一个action,因此又在render以后,从新接收到了新的store,这样,就又会从新渲染出来新的render。 咱们能够发现,这个 fiterdSug 是存在的,可是以前若是没有赋默认值,那么就在前面报错了,也就没有后面的步骤了。
问题缘由:
其实如今就比较好理解了,问题就是处在 reducer 那里,咱们在获取到全部的建议的时候,并无把当前 state 的全部值返回到一个新的state了,因此就致使了在进入 detail 页面的时候,接收不到 filteredSug,解决问题的方式很简单,以下:
function handleSuggestions (state = {allSuggestions: [], filteredSug: []}, action) { switch (action.type) { case 'ADD_ALL_SUGGESTIONS': const newSug = Object.assign([], action.data); return { allSuggestions: newSug, filteredSug: [] } case 'FILTER_SUGGESTION': return { allSuggestions: state.allSuggestions, filteredSug: state.allSuggestions.filter(function (item, index) { return item.suggestionId == action.id; }) } default: return state; } }
这样,咱们就能够在render中使用filterSug的时候不须要使用默认值,而后就map了,由于开始map的时候,什么都没有,因此就不会渲染,而后store接收到action以后,触发了新的state,这样就可使得咱们的filterSug成为了一个新的值,页面就会渲染出来了。
那么 connect 的这个源码是怎样的呢? 为何在进入页面的时候,能够保证在 componentWillMount 钩子函数以前就能够已经获取到了 store 中的state呢?
猜测一: 因为在react中的组件里,constrctor钩子函数式最先被调用的,因此这里获取store的步骤多是在 constructor 时调用的。 由于connect是react-redux的方法, 而react-redux是另一个库,因此只能利用react的现有的api。
测试验证
方法: 在constructor钩子函数中打印一下时间,再在 connect 的相关函数中打印一下时间, 若是说 connect 中的时间在后,那么就是对的。
这个结果很意外,为何能够首先得到 state 呢? 不是应该首先得到conscructor的吗? 而后利用这个钩子函数获取到state?
猜测二: 既然state是最早获取到的, 那么就是说它在原来的组件的基础上包装了一层。
这个确实很简单了,在最开始学习redux的过程当中,就已经学习到了下面的概念:组件包括UI组件和容器组件,前者的做用彻底是用于展现的, 而容器组件的做用就是使用状态管理工具如redux来命名的,即在使用redux时,就须要先将全部的数据以props的形式传递到各个容器组件中(实际上并非这样的,在react中,有一个context的概念,就是传递数据,不须要使用props层层传递,而只须要使用一个 context 就能够以最快的速度把想要传递的数据给到各个子组件中了), 而后容器组件再将之做为props传递给UI组件, 而且咱们在使用redux时,不管是发送dispatch,仍是接受store中的数据,都须要经过这一层。
到这里这个问题就很清楚了,就是首先,connect的容器组件首先将获取到的数据传进来放在props中,而后再开始建立内部的UI组件,而后内部须要相应的props时,直接从这个中间层来获取就能够了。因此constructor的创造时间是晚于state传递进来的时间的。