React + Koa2打造『官方管理后台』10 总结

一.项目架构

(1).前端

(2).后端

项目架构没有变化增加了如下:

1.err_config 负责返回各个请求成功的状态码及失败的状态码

2.控制器中分别多出了admin.js和index.js用于调用接收传递的参数且调用各个业务接口,返回结果至前端

3.routes 多了admin.js与index.js两个路由

二.关键技术点

(1).React中是怎么保存数据的

react通过constructor来保存数据的,

获取数据通过this.state.xx进行访问

修改数据通过this.setState{}进行修改

第一种 传入新的 state 对象

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的事件传递为在父组件中引入子组件,在子组件的标签上 事件名={事件名}

子组件通过props接收即可

这其中牵扯到this指向的问题

  1. 第一种是通过bind强行绑定
  2. 第二种箭头函数拿的是父级的作用域
  3. 第三种直接绑定箭头函数执行的方式,是在点击的时候执行的,点击谁this就指向谁

丢失问题:是因为函数作为参数时一定会造成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为开发环境

  1. Dev:开发环境,该环境下的配置项只影响开发人员本地代码配置,在项目初期代码本地编写时调试使用
  2. Test:测试环境,该环境配置影响整个团队的测试环境
  3. Production:正式生产环境,程序最终发布后所需要的参数配置

const ENV = process.env.NODE_ENV;
module.exports={
    isDev:ENV === 'dev',
    isPrd:ENV === 'production'
}

(8).是怎么实现密码加密的

通过crypto进行加密node自带的

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

path.appSrc为根路径

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路由

创建普通路由

react路由需要先安装react-router

在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;

创建子路由

通过render={props =(<父组件>....)}

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如何请求数据

首先需要下载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是做什么的

安装序列化data qs

将URL解析为对象

  

 const Qs = require('qs'); 
      let url = 'method=one&projectId=85&appToken=abc';
      Qs.parse(url);
      
      //输出结果
      {
          method:'one',
          projectId:'85',
          appToken:'abc'
      }

将对象解析序列化为url

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">&#xe68b;</i>
                  </Link>
            </div>
      
        )
    }
}

被动跳转

通过history.push进行跳转

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请求传递过来的账号密码经过一些列的合法验证后调用登录接口

其中密码再通过makeCrypto进行进行加密

同时也通过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钩子函数中调用

(17).如何跨域设置cookie

前端的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).如何进行组件化/如何抽离公共组件

组件化:

1.根据页面进行分割

2.再通过不同功能点进行分割

公共组件:

将头部和下滑栏等组件抽离再在各个功能点的index.js中引入即可

(20).组件间如何完成数据联动

父组件中有该数据 以及修改该数据的方法

将这两个数据传递到子组件,子组件通过数据进行渲染,事件也进行绑定

当子组件事件触发将index传入父组件方法,父组件进行数据修改重新setState

(21).如何解决爬取第二页的问题

如果传递过来的爬虫文件中参数有等于course的则通过

waitForSelector

click

waitFor

await pg.evaluate(options.callback);

再循环把之前爬取的参数进行循环push即可,如下

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
}))

退出的时候删除ctx.session即可

 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中再调用根目录下的组件

五.项目心得

此项目主要难点为组件之间的拆分,axios跨域的配置请求,登录的信息存储,以及route的路由配置,api的拦截

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值