一.项目架构
(1).前端
(2).后端
1.err_config 负责返回各个请求成功的状态码及失败的状态码
2.控制器中分别多出了admin.js和index.js用于调用接收传递的参数且调用各个业务接口,返回结果至前端
3.routes 多了admin.js与index.js两个路由
二.关键技术点
(1).React中是怎么保存数据的
this.setState({
msg: "我被改变了"
});
第二种 传入回调函数,并在回调函数里面返回新的 state 对象
handleClick(index) {
this.setState(state => {
state.list[index].isLike = !state.list[index].isLike;
return {
list: state.list
};
});
}
this.setState(state => ({ msg: "hello world" }));
当通过对象传入的时候如果一个函数中有两个对象传入则出于性能方面考虑,将多次setState调用合并为一次,所以并不会直接对每次调用都更新。如果传入的对象都有相同的key,那么最后一次传入的对象的那个key值会是最终值。
当通过函数传递时的时候如果一个函数中有两个函数传入React则会按照各个setState的调用顺序,将他们放入到一个队列中,然后在更新的时候时会将上一个调用结束时产生的state传入到下一个setState中。
也就是说如果在一个函数中想要连续的通过setState来操作同一个key的属性就必须要传入参数
setState 方法还提供一个可选的参数 callback ,即一个回调函数,会在当前调用 setState 方法更新状态后进行调用
Component.prototype.setState = function (partialState, callback) {
// ...
};
(2).React中是怎么做到事件传递的
react的事件传递为在父组件中引入子组件,在子组件的标签上 事件名={事件名}
丢失问题:是因为函数作为参数时一定会造成this丢失,会打印undefined
<TableBody collectionData = {collectionData}
onStatusClick = {this.onStatusClick.bind(this)}
></TableBody>
(3).React是怎么做到父子组件引用且最后渲染到页面上的
子页面上通过抛出
export default class xxx extends Componet{
render(){
return(
xxxx
)
}
}
父页面上通过引入
import xxfrom 'xx'
render(){
return(
<xxx></xx>
)
}
(4).脚手架工具
npm i create-react-app -g
create-react-app todo
npm start
(5).Redis是怎么在React中使用的
安装好redis依赖的包后通过redis.createClient来创建链接,再封装redis的get set方法抛出使用即可
(6).ejs有什么用
1.用JavaScript创建HTML字符串 ,在JavaScript中拼字符串的缺点是可维护性不好。而使用模板可以让你通过代码的空行和缩进来清楚地展现出你的HTML。
2.基于WebService的AJAX网站开发 EJS可以接收WebService异步传送过来的JSON格式的数据,将这种数据直接传入你的模板里,然后将结果插入到你的页面中。你所需要做的只是通过以下代码:
new EJS({url: 'comments.ejs'}).update('element_id', '/comments.json')
(7).如何区分生产环境与开发环境的配置
通过process.env.NODE_ENV来判断true为开发环境
- Dev:开发环境,该环境下的配置项只影响开发人员本地代码配置,在项目初期代码本地编写时调试使用
- Test:测试环境,该环境配置影响整个团队的测试环境
- Production:正式生产环境,程序最终发布后所需要的参数配置
const ENV = process.env.NODE_ENV;
module.exports={
isDev:ENV === 'dev',
isPrd:ENV === 'production'
}
(8).是怎么实现密码加密的
crypto = require('crypto')
function makeCrypto(str){
const _md5 = crypto.createHash('md5'),
content = `str=${str}&secret=${cryptoSecret}`;
return _md5.update(content).digest('hex')
}
const {adminInfo} = require('../config/config'),
{addAdmin} = require('../service/admin'),
{makeCrypto} = require('../lib/util');
class Admin {
async createAdmin(){
adminInfo.password = makeCrypto(adminInfo.password)
const result = await addAdmin(adminInfo);
if(result){
console.log(0);
}else{
console.log(1);
}
}
}
(9).如何在react中配置别名
node_modules\react-scripts\config\webpack.config.js搜索alias
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
'@':path.appSrc,
'pages':path.appSrc+'/pages',
'components':path.appSrc+'/components',
'assets':path.appSrc+'/assets',
'services':path.appSrc+'/services',
'utils':path.appSrc+'/utils'
},
(10).如何创建react路由
在appjs中引入 BrowserRouter和Route,Wwitch
在return()中通过固定格式编写 component来指定页面 path来指定路径
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom'
import { Route, Switch, NavLink, Link } from 'react-router-dom'
import IndexPage from './pages/index'
import LoginPage from './pages/login'
function App() {
return (
<Router>
<Switch>
<Route component={LoginPage} path="/login"></Route>
<Route component={IndexPage} path="/index"></Route>
</Switch>
</Router>
);
}
export default App;
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom'
import { Route, Switch, NavLink, Link } from 'react-router-dom'
import IndexPage from './pages/index'
import LoginPage from './pages/login'
import DetailPage from './pages/sub/detail'
import ListPage from './pages/sub/list'
function App() {
return (
<Router>
<Switch>
<Route component={LoginPage} path="/login"></Route>
<Route path="/index" render={ props =>(
<IndexPage>
<Switch>
<Route component={DetailPage} path="/sub/detail"></Route>
<Route component={ListPage} path="/sub/list"></Route>
</Switch>
</IndexPage>
)}/>
</Switch>
</Router>
);
}
export default App;
(11).项目的各个功能点页面是怎么编写的
各个功能的页面通过组件拆分的方式编写,以首页为例,分为三个组件分别为头部组件 左侧导航组件 和列表内容页组件,其中内容页组件又拆分为主组件 内容组件
(12).axios如何请求数据
import axios from 'axios';
import qs from 'qs';
export default class HTTP{
axiosPost(options){
axios({
url:options.url,
method:'post',
withCredentials:true,
header:{
'Content-Type':'application/x-www-form-urlencoded'
},
data: qs.stringify(options.data)
}).then((res)=>{
options.success(res.data)
}).catch((err)=>{
options.error(err)
})
}
axiosGet(options){
axios({
url:options.url,
withCredentials:true,
method:'get',
}).then((res)=>{
options.success(res.data)
}).catch((err)=>{
options.error(err)
})
}
}
(13).koa2如何实现跨域请求
npm i koa2-cors -S
在服务端的app.js中引入
const cors = require('koa2-cors');
app.use(cors({
origin:function(ctx){
return 'http://localhost:3001'
}
}))
(14).qs是做什么的
const Qs = require('qs');
let url = 'method=one&projectId=85&appToken=abc';
Qs.parse(url);
//输出结果
{
method:'one',
projectId:'85',
appToken:'abc'
}
npm i qs -S
const Qs = require('qs');
let obj= { method: "one", projectId: "85", appToken: "abc"};
Qs.stringify(obj);
//结果
method=one&projectId=85&appToken=abc
JSON.string ify与 qs.stringify 的区别
json.stringify
{"uid":"cs11","pwd":"000000als","username":"cs11"}
qs.stringify
uid=cs11&pwd=abc&username=cs11
(15).React如何做页面跳转
需要先引入Link 再通过<Link to={}>进行 跳转
import React,{Component} from 'react';
import {Link} from 'react-router-dom'
import './index.scss';
export default class NavItem extends Component{
constructor(props){
super(props)
}
render(){
const {curIdx,index,dataItem,onNavItemClick} = this.props;
return(
<div className={['nav-item',index===curIdx?'nav-current':''].join(' ')}>
<Link
to={`/${dataItem.field}`}
onClick={()=>onNavItemClick(dataItem,index)}
>{dataItem.title}
<i className="iconfont iconright"></i>
</Link>
</div>
)
}
}
async loginCheck(){
const result = await loginservice.loginCheck();
const errorCode = result.error_code;
const {history} = this.props;
if(errorCode === 10006){
history.push('/login')
return
}
history.push('/course')
}
这个history需要从APP.js向下传递
render={ props =>(
<IndexPage history={props.history}>
children
代表获取组件的所有子节点可通过它和router结合进行子路由的跳转
(16).如何验证登录
通过ctx.request.body解析ajax请求传递过来的账号密码经过一些列的合法验证后调用登录接口
同时也通过logincheck方法来验证是否当前session已经存储
async loginCheck(ctx,next){
if(ctx.session && ctx.session.userInfo){
ctx.body = returnInfo(LOGIN.LOGIN_STATUS)
return
}
ctx.body = returnInfo(LOGIN.NOT_LOGIN_STATUS)
}
在index.js的componentDidMount钩子函数中调用
前端的ajax请求中增加 withCredentials:true设置为使跨域请求提供凭据信息
后端设置app.usecros的地方也要设置withCredentials:true
(18).接口权限验证怎么做的
通过异步函数的ctx.session 和next即可实现,当检测到ctx.session存在时 则执行next 函数。将此方法封装起来,在node的router中的第二个参数中加入此方法,即可拦截执行控制器
const {returnInfo} = require('../lib/util'),
{LOGIN} = require('../config/err_config');
module.exports = async(ctx,next)=>{
if(ctx.session.userInfo){
await next();
return
}
ctx.body = returnInfo(LOGIN.NOT_LOGIN_STATUS)
}
router.get('/get_courses',loginCheck,indexController.getCourseData)
(19).如何进行组件化/如何抽离公共组件
将头部和下滑栏等组件抽离再在各个功能点的index.js中引入即可
(20).组件间如何完成数据联动
将这两个数据传递到子组件,子组件通过数据进行渲染,事件也进行绑定
当子组件事件触发将index传入父组件方法,父组件进行数据修改重新setState
(21).如何解决爬取第二页的问题
await pg.evaluate(options.callback);
let result = await pg.evaluate(options.callback);
if(result && options.field === 'course'){
await pg.waitForSelector('.page-btn.page-last')[0];
await pg.click('.page-btn.page-last');
await pg.waitFor(2000);
const res = await pg.evaluate(options.callback);
await pg.waitFor(2000);
for(var i = 0; i < res.length; i++){
await result.push(res[i]);
}
}
三..主要功能点实现
(1).你是怎么实现整个登录/退出功能的
首先用户输入用户名密码,提交至后台,通过crypto进行密码加密在后台进行验证,通过后通过ctx.session进行检测,由于在app.js中的配置,会将传递过来的cookie进行redis存储,那么你在二次登录的时候由于在app.js中配置了store是reidis所以通过ctx.session直接去判断redis中是否有这个存储的session信息
redis存储用户信息非常的好的原因是可以支持多个服务器的负载均衡,而且性能好。
session 毕竟是直接存储在服务器上的,redis实际上是用于缓存,所以redis,更适合存储用户信息这一类数据
app.keys = sessionInfo.keys;
app.use(session({
key:sessionInfo.name,
prefix:sessionInfo.prefix,
cookie:cookieInfo,
stroe:koaRedis(redisInfo) //session上所有需要挂载的信息的存储工具是redis
}))
async logoutAction(ctx,next){
delete ctx.session.userInfo;
ctx.body = returnInfo(LOGIN.LOGOUT_SUCCESS)
}
(2).你是怎么样发出ajax请求,处理响应的
通过封装好的axios通过sq序列化参数,通过Koa-crose在app.js中设置跨域发出
所有的返回值全部封装起来进行返回,前端再判断返回的code是多少从而进行响应
(3).整个页面的固定文本你是怎么处理的
这个页面的提示框,表头等等信息全部封装为数组,在每一个应用页面进行遍历输出
(4).整个页面是怎么实现只切换下边的tab页面的
通过children参数和route标签进行实现,将所有子节点路由配置在indexpage下,这时如果点击某一个标签则会跳转至index.js页面将dom通过children进行传递 这事再通过提取的Continar组件进行渲染显示即可
四.编写原理
各个子组件在pages下编写,如果需要用到公共组件则调用components文件下的公共组件即可,这时各个子组件再进行详细的拆分书写,最终返回给pages下根目录的组件中而在appjs中再调用根目录下的组件