React.js学习笔记(19) 前后端联调 + ( proxy代理 ) + ( axios拦截器 ) + ( css Modules模块化方案 ) + ( css-loader ) + ( 非路由组件如何使用history ) + ( bodyParser,cookieParser中间件 ) + ( utility MD5加密库 ) + ( nodemon自动重启node ) + ( Array.find() 和 Array.findIndex() ) + ( browser-cookies库 )

(1) proxy

前端的端口在:localhost:3000
后端的端口在:localhost:1234
所以要在webpack中配置proxy选项 (proxy是代理的意思)

在package.json中添加如下配置-------这里用的是create-react-app脚手架eject后的项目


  "proxy":"http://localhost:1234"  // 把前端的请求都代理到1234端口,和后端一致,即可访问后端接口


(2) axios

配置好proxy后,就可以用axios跨域了

在组件中



import React,{Component} from 'react';
import { Redirect } from 'react-router-dom';
import axios from 'axios';                                   // 引入axios


export default class Login extends Component {
    goLog = () => {
        this.props.goLogin();
    }
    goGetData = () => {
        axios.get('/data')                                   // 使用axios
            .then(res => 
                { console.log(res,'res')} 
            )
    }
    render() {
        return (
            <div>
                登陆页面
                {
                this.props.login && this.props.login.isLogin ? <Redirect to="/user" /> : null
                }
                <div onClick={this.goLog}>
                    点击登陆
                </div>
                <div onClick={this.goGetData}>
                    点击---用axios获取后端数据
                </div>
            </div>
        )
    }
}

(axios中文文档) https://www.kancloud.cn/yunye/axios/234845

(3)axios拦截器

作用:当一个请求发出的时候,会先流过 interceptors 的 request 部分,接着请求会发出,当接受到响应时,会先流过 interceptors 的 response 部分,最后返回

  • interceptor是拦截器的意思
  • 作用:在请求或响应被 then 或 catch 处理前拦截它们。

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });



如果你想在稍后移除拦截器,可以这样:
var myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);



可以为自定义 axios 实例添加拦截器
var instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});

  • 实例:如下
(1) config.js文件


import axios from 'axios';

import {Toast} from 'antd-mobile';         // antd-mobile轻提示组件Toast

// 拦截请求
axios.interceptors.request.use(config => {
    Toast.loading('加载中',1)              // loading组件,显示文字加载中,自动关闭延时1s
    console.log('request go');
    return config;
}, err => {
    console.log('请求失败')
    return Promise.reject(err)
})

//拦截响应
axios.interceptors.response.use(config => {
    Toast.hide()                             // 销毁Toast组件
    console.log('response get')
    return config;
}, err => {
    console.log('响应失败')
    return Promise.reject(err)
})




-------------------------------------------------------------
(2) 在入口文件index.js文件中引入上面的cofig.js文件


import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import {Provider} from 'react-redux';
import {store} from './store/store.js';
// import UserContainer from './component/user/container.js';
import './config/config.js';

import {BrowserRouter} from 'react-router-dom'

ReactDOM.render(
    <Provider store={store}>
        <BrowserRouter>
            <App></App>
        </BrowserRouter>
    </Provider>
, document.getElementById('root'));
registerServiceWorker();

antd-mobile https://mobile.ant.design/components/toast-cn/

(4) css Modules模块化方案

  • 支持less和sass的语法
  • 解决命名冲突污染等问题
  • 使用JS和CSS分离的写法,不会改变大家的书写习惯
  • 解决依赖管理不彻底,无法共享变量,代码压缩不彻底
    使用webpack项目中,只需要简单的配置,如下:
webpack.config.dev.js

注意:这里需要提前安装css-loader插件!!

{
    module: {
       rules:[
          {
            test: /\.css$/,
            use: [
              {
                loader: 'style-loader'
              },
              {
                loader: 'css-loader?modules',     // 在css-loader后添加 ?modules即可
                 // loader: 'css-loader?modules&localIdentName=[name]-[hash:base64:5]'
                 // modules后面还可以跟具体的命名规则
                 // localIdentName 是设置生成样式的命名规则。
              }
            ]
  
          }
       ]
    }
}
------------------------------------------------------------------------------
换一中写法:(一样的)


      {
        test: /\.css$/,
        exclude: /node_modules\/antd/,
        use: [
          require.resolve('style-loader'),
          {
            loader: require.resolve('css-loader'),
            options: {
             importLoaders: 1,  // 在css-loader前应用的loader的数目, 默认为0
             modules:true,  // 开启css-modules模式, 默认值为flase
             localIdentName:'[name]-[local]-[hash:base64:8]',//css-modules模式下local类名的命名
            },
          },
        ]
      },

https://segmentfault.com/a/1190000010301977
(详细)https://www.cnblogs.com/kugeliu/p/7889018.html
(阮一峰)http://www.ruanyifeng.com/blog/2016/06/css_modules.html

(5) Ant Design Mobile (antd-mobile)

  • 按需加载
    除了安装 ( antd-mobile ) 之外,还需要安装 ( babel-plugin-import )
  • 这里有个坑:在package.json中配置babel的时候(babel-plugin-import插件在安装后,需要配置babel ),配置完,引入ant组件使用,样式会失效!!!而在未使用css-modules模块化方案的时候,ant-mobile能正常使用,( 要使用css-modules的话,要在webpack.config.json中做如下配置:(!)
    (踩坑与填坑) https://segmentfault.com/q/1010000011965218
(!)


 {
        test: /\.css$/,
        include: /node_modules\/antd/,
        use: [
          require.resolve('style-loader'),
          {
            loader: require.resolve('css-loader'),
            options: {
              modules:false
            },
          },
        ]
      },
      {
        test: /\.css$/,
        exclude: /node_modules\/antd/,
        use: [
          require.resolve('style-loader'),
          {
            loader: require.resolve('css-loader'),
            options: {
              importLoaders: 1,
              modules:true,
              localIdentName:'[name]-[local]-[hash:base64:8]',
            },
          },
        ]
      },

安装:
cnpm install antd-mobile --save



使用:
import { Button } from 'antd-mobile';
import 'antd-mobile/dist/antd-mobile.css'; 



按需加载:
(1)安装babel按需加载插件 babel-plugin-import

cnpm install babel-plugin-import --save-dev

(2)在create-react-app脚手架eject后,package.json文件中,配置如下:
// 自己搭建可以写在.babelrc中
 "babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      "transform-decorators-legacy",
     ["import", { "libraryName": "antd-mobile", "style": "css" }]
    ]
  }



(3) 使用
import { Button } from 'antd-mobile';
...
...



(4) 因为使用了css-modules模块化方案,所以在配置packageg.json中babel的时候,要修改成如下配置:


 {
        test: /\.css$/,
        include: /node_modules\/antd/,
        use: [
          require.resolve('style-loader'),
          {
            loader: require.resolve('css-loader'),
            options: {
              modules:false
            },
          },
        ]
      },
      {
        test: /\.css$/,
        exclude: /node_modules\/antd/,
        use: [
          require.resolve('style-loader'),
          {
            loader: require.resolve('css-loader'),
            options: {
              importLoaders: 1,
              modules:true,
              localIdentName:'[name]-[local]-[hash:base64:8]',
            },
          },
        ]
      },

(6) css-loader 和 ( style-loader, postcss-loader )

  • css-loader: 在js中加载css
  • style-loader: 把加载的css作为style标签内容插入到html中
  • postcss-loader:
    如果某些css要考虑到浏览器的兼容性(比如css3中的flex),我们要webpack在打包的过程中自动为这些css属性加上浏览器前缀,这时就用到了postcss-loader和它对应的插件autoprefixer。
    http://blog.csdn.net/szu_aker/article/details/72588857

(7) 非路由组件如何使用this.props.history

如果是路由组件,访问history一般都是通过this.props.history来操作history

  • 而非路由组件通过 {withRouter} 来使用this.props.history
import React from "react";
import {withRouter} from "react-router-dom";  // 引入withRouter

class MyComponent extends React.Component {
  ...
  myFunction() {
    this.props.history.push("/some/Path");
  }
  ...
}
export default withRouter(MyComponent);  // 用withRouter包裹该class


----------------------------------------------------------------------------

this.props.location.pathname  获取当前的url

如何使用history跳转路由(全):https://segmentfault.com/a/1190000011137828

(8) body-parser中间件

可以通过body-parser对象创建中间件,当接受到客户端请求时所有的中间件都会给req.body添加属性,请求内容为空时,解析为空或者错误。

  • body-parser是非常常用的一个express中间件,作用是对post请求的请求体 ( req ) 进行解析。

https://www.cnblogs.com/chyingp/p/nodejs-learning-express-body-parser.html
(详细) https://www.cnblogs.com/taiyanghua0522/p/7054781.html

server.js


const express = require('express');
const bodyParser = require('body-parser');      // 引入body-parser
const cookieParser = require('cookie-parser');  // 引入cookie-parser

const app = express();
const user = require('./user.js');


app.use(bodyParser.json())                   // 使用body-parser
app.use(cookieParser())                      // 使用cookie-parser

app.use('/one', user)
app.get('/', function(req, res){
    res.send('<p>后端页面</p>')
})


app.listen(3333, function() {
    console.log('express port 1212 is going')
})





----------------------------------------------------
(存入cookie)

user.js


Router.post('/login', function(req,res){
    const {user, password} = req.body;    // req.body是body-parser解析的请求体
    console.log(req.body, 'req.body')
    User.findOne({user,password: MD5PASSWORD(password)}, function(err,doc){
       if(!doc){
           return res.json({
               code:1,
               msg: '用户名或密码错误'
           })
       }
       res.cookie('userId', doc._id)   // cookie-parser的使用--------写入cookie
                                      // login接口存入一个cookie,name是userId,value是res._id 

                                      // 存cookie是在res中
                                      // 取cookie是在req中
       return res.json({
           code:0,
           data: doc
       })
    })
})




----------------------------------------------------
(取出cookie)

Router.get('/info',function(req,res){
    // 用户有没有cookie
    const {userId} = req.cookies;   // 取出cookie,中的userId-------取出cookie,注意是复数!!!!

    if(!userId) {
        return res.json({
            code:1
        })
    }

    User.findOne({_id: userId}, function(err,doc){
        if(err) {
            return res.json({
                code:1,
                msg: '后端出错'
            })
        }
        return res.json({
            code:0,
            data: doc    
        })
    })
    
})


(9) cookie-parser中间件

cookies最常用在‘记住密码’和‘自动登录’。
cookies 存在于客户端,安全性较低,一般要存入加密后的信息,并且大部分情况下需要设置过期时间或不使用删除。

  • cookie类似于一张身份卡,登陆后,由服务端返回,带着cookie即可访问受限资源

  • 存cookie


res.cookie('userId', doc._id)     // name是userId,value是doc._id

  • 取cookie-------------注意:是复数 req.cookies


const {userId} = req.cookies
// const userId = req.cookies.userId

(10) utility

md5加密库


安装:
cnpm install utility --save

引入和使用:
const utility = require('utility');

Router.post('/register', function(req,res){

    console.log(req.body,'req.body');

    const {user, password, type} = req.body;
    User.findOne({'user':user},function(err,doc){
        if(doc){
            return res.json({
                code:1,
                msg: '用户名存在'
            })
        }
        User.create({
            'user': user,
            'password':utility.md5(password),     // 使用--------------
            'type':type
        },function(err,doc){
            if(err){
                return res.json({
                    code:1,
                    msg:'后端出错了'
                })
            }
            console.log('用户名不存在,已添加到数据库')
            return res.json({
                code:0
            })
        })
    })
})

----------------------------------------------------

后端比较合格的加密函数:


function MD5PASSWORD(password) {
    const salt = 'more_complex_passWord187873871~@!@@@##$$%%DAxiao';
    return utility.md5( utility.md5(password + salt) );
}

(11) nodemon

原始node中的express框架,每次修改js代码后,都要重新手动启动才能看到改动后的效果,调试起来十分不方便。所以我引入了nodemon模块了弥补这样缺点。

  • 全局安装
 cnpm install nodemon -g
  • 修改配置


在package中配置的script中配置的代码 :

“node”: "node server/server.js"

------
修改为

"node": "nodemon server/server.js"


(12) Route组件中的component可以是一个函数

import React from 'react';
import ReactDOM from 'react-dom';


function Boss() {
    return (
        <div>
            boss页面
        </div>
    )
}

ReactDOM.render(
    <Provider store={store}>
        <BrowserRouter>
            <div>
                <AuthRoute></AuthRoute> 

                <Route path='/boss' component={Boss}></Route>   
                // 这里的Boss是一个函数,(不管是function还是class,都是return返回值)

                <Route path='/register' component={Register}></Route> 
                <Route path='/login' component={Login}></Route>
            </div>
        </BrowserRouter>
    </Provider>
, document.getElementById('root'));
registerServiceWorker();

(13) 登陆跳转的逻辑

(1) 注册时拿到注册的所有信息,把信息传到redux中,先做前端判断(用户名和密码是否为空,密码输入是否一致 等),然后在redux的action请求后端user/login接口,传入注册信息到请求体中,规定如果后端返回res.status===200&&res.data.code=0就表示注册成功,把注册信息写入redux的reduce中,否则表示注册失败,并且由后端返回错误信息,并且存入redux,然后取出显示在前端页面 。 ----后端注册接口的逻辑:拿到前端传过来的请求数据,然后查找数据库时候存在,如果存在,表示已经注册,返回code===1,和msg=已经注册过了。否则表示未注册,则把前段数据存入数据库。并返回code表示注册成功
(2) 注册后的跳转逻辑:根据注册时的身份(genius或者boss) 和 (是否有头像) 决定跳转到 boss页面,genius页面, bossinfo页面,geniusinfo页面

(3) 登陆时,拿到登陆的所有信息,然后做前端判断(用户名,密码是否为空),然后请求后端user/login接口,传入如果返回 res.status===200 && res.data.code ===0表示登陆成功,然后拿到后端返回的数据,存入redux的reducer中。否则表示登陆失败,吧后端返回的错误信息,存入redux,返回给前端页面。--------后端接口:拿到前段给到的请求体,然后查找数据库,如果doc不存在,就返回res.jsoln({code:1}),否则表示用户信息在数据库中存在,这是存入cookie,res.cookie('名字',doc._id),然后返回数据库中存在的用户信息,返回给前段接口使用
(4) 登陆后的跳转逻辑:和注册时候一样
(5) 如果是其他页面,不在login,和register两个页面中,则在前段请求另一个接口,ingo接口,,如果后端返回code是0,res.status是200,表示有用户信息,则拿到后端返回的数据存入redux, 否则表示没有用户信息,路由跳转到登陆页面。-------后端接口逻辑:首先在该接口验证cookie是否存在,cost {usrId} = req.cookies,如果不存在返回res.json({code:1}), 然后查找数据库,有数据返回数据,没有返回相应的状态码

(14) mongoose添加的两种方式

(1) create方式 
缺点: 不能存储id

User.create({
            user,
            password: MD5PASSWORD(password),
            type
        }, function(err,doc){
            if(err) {
                return res.json({
                    code:1,
                    msg: '后端出错'
                })
            }
            return res.json({
                code:0
            })
        })


--------------------------------------------


(2) new mongoose.model().save方式:




Router.post('/register', function(req,res) {
    console.log(req.body)
    const { user, password, type } = req.body;
    
    User.findOne({user}, function(err,doc){
        if(doc) {
            return res.json({
                code: 1,
                msg: '已经注册过了'
            })
        }
        const userModel = new User({         // new mongoose.model('user')
            user,
            password: MD5PASSWORD(password),
            type
        })
        userModel.save(function(err,doc){    // .save
            if(err){
                return res.json({
                    code:1,
                    msg:'后端出错'
                })
            }
            const {user, type, _id} = doc
            res.cookie('userId', _id)       // 在注册页面存cookie
            return res.json({
                code:0,
                data:doc
            })

        })
        

    })
   
    
})

Entity结构 http://blog.csdn.net/sinat_25127047/article/details/50560167

(15) mongoose相关语法

  • findByIdAndUpdate

Router.post('/update', function(req,res) {
    const {userId} = req.cookies;
    console.log(req.cookies,'req.cookies');
    if(!userId) {
        return json.dumps({
            code:1
        })
    }
    const body = req.body
    // findByIdAndUpdate(需要查找的id,需要更新的数据,回掉)
    User.findByIdAndUpdate(userId,body,function(err, doc){      --------------------- 
        const data = Object.assign({},{
            user: doc.user,
            type: doc.type
        },body); // 把body对象 和user type 合并赋值给data------body是前端请求的实时更新的请求体
        return res.json({
            code:0,
            data
        })
    })
})

(16) Array.find()

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。

  • 找出第一个符合条件的数组成员,返回该成员
  • 参数是回调函数fallback(value,index,array)
  • 没有找到符合的成员,返回undefined
  • 还可以接受除fallback回调函数之外的,第二个参数,用来绑定回调函数的this对象。

[1, 4, -5, 10].find(n => n < 0)

// -5

---------------------------------------------------

[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。


---------------------------------------------------

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26

(17) Array.findIndex()

数组实例的findIndex方法返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

  • 找出第一个符合条件的数组成员,返回该成员的位置
  • 参数是回调函数fallback(value,index,array)
  • 没有找到符合数组成员的位置,返回-1
  • 还可以接受除fallback回调函数之外的,第二个参数,用来绑定回调函数的this对象。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) 

// 2

(18) nodeJS 取参

  • req.body ----------是post请求,获取参数
  • req.query----------是get 请求,获取参数
  • req.params
  • req.param()

(19) browser-cookies

git地址:https://github.com/voltace/browser-cookies
安装:cnpm isntall browser-cookies -S

  • cookies.set 设置cookie
  • cookies.get 获取cookie
  • cookies.erase 清除cookie ------------- erase是清除,抹去的意思
var cookies = require('browser-cookies');

cookies.set('firstName', 'Lisa');
cookies.set('firstName', 'Lisa', {expires: 365}); // Expires after 1 year
cookies.set('firstName', 'Lisa', {secure: true, domain: 'www.example.org'});

cookies.get('firstName'); // Returns cookie value (or null)

cookies.erase('firstName'); // Removes cookie

(20) window.location.href = window.location.href强制刷新页面

http://blog.csdn.net/github_37483541/article/details/59481084


    注销登陆:

    logout =() => {
        // erase是清除,抹掉的意思
        // browserCookies.erase('userId');
        // window.location.href = window.location.href
        const alert = Modal.alert 
        alert('注销','确定要注销登陆吗?',[
            {text:'取消', onPress: () => console.log('canscel')},
            {text:'确定', onPress: () => {
                browserCookies.erase('userId');
                // window.location.href = window.location.href
                this.props.logoutAll(); // 该方法清除redux中register存着的数据
            }}
        ])
    }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容