react--随笔 1

REACT

介绍

一款javascript前端框架,把用户界面抽象成一个个的组件,按需组合成页面,官网,与其他框架的共同点是,都采用虚拟dom,和数据驱动

angularJsreactJsvueJsangularTs
控制器--弱化
过滤器-
指令-
模板语法-
服务--
组件-
jsx-加入-

angularJs 双向绑定,依赖注入 reactJs一切皆组件 VueJs 小而美 angularTs 大而全

环境搭建

官方脚手架

安装 yarn,重启(不重启,别的盘符有可能用不了)

//查询当前镜像
yarn config get registry 
//设置为淘宝镜像
yarn config set registry https://registry.npm.taobao.org/
//设置为官方镜像
//yarn config set registry https://registry.yarnpkg.com

官网 [create-react-app](https://create-react-app.dev/docs/getting-started

创建 react项目

yarn create react-app 目录 | npx create-react-app 目录 | npm init react-app 目录

yarn eject   解构出所有的配置文件 可选
yarn start |  npm start 			开发
yarn build |  npm run build	 打包

//调试 需要安装给chrome浏览器一个插件 react-dev-tools  谷歌助手->chrome商店-搜索

环境解析

  • react: 核心包,解析组件 演示
  • react-dom: 编译 -> 浏览器 演示
  • react-scrpts: react的项目环境配置
  • manifest.json 生成一个网页的桌面快捷方式时,会以这个文件中的内容作为图标和文字的显示内容
  • registerServiceWorker.js支持离线访问,所以用起来和原生app的体验很接近,只有打包生成线上版本的react项目时,registerServiceWorker.js才会有效。服务器必须采用https协议
  • 对Internet Explorer 9,10和11的支持需要polyfill。

环境配置和错误处理

运行yarn 提示“不是内部或外部命令,”
	装完重启
  
create-react-app 提示“不是内部或外部命令,” //yarn无法全局安装包
	npm i create-react-app  -g      用npm重装
  
create-react-app 能用安装到一半报错(error)
	node 全量安装  ,一路下一步安装过程中有个复选框选中,时长30分钟


报缺少babel 包: 安装一下(yarn add xxx -S)

配置
修改端口
//修改script/start.js
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3001;

去除eslint 警告
//config/webpack.config.js
//注释关于eslint的导入和rules规则 (react16.x)
//注释关于new ESLintPlugin (react17.x)

第三方脚手架

yomen/umi

webpack手动搭建

资源限制

  • 本地资源导入(import) 不可以导入src之外的包
  • 相对 路径以文件所在位置为基准顶层到src,绝对路径 的根是 public目录
  • 前景图片, 相对 和 绝对路径 都指向了 public目录(因为是数据)

JSX

jsx是一个 JavaScript 的语法扩展,可以理解为js的一个新的数据类型,出现在js当中,文件为xx.js|xx.jsx

var b= <strong>强壮</strong>

语法要求

  • 标签要闭合
  • 元素必须要有一个顶层元素
  • 变量首字母大写代表组件,小写对应是js数据类型
  • 属性,小驼峰命名 <xx tabIndex="2">

JSX 是一个 JavaScript 语法扩展。它类似于模板语言,但它具有 JavaScript 的全部能力。JSX 最终会被编译为 React.createElement() 函数调用,返回称为 “React 元素” 的普通 JavaScript 对象

es6

class Person2223{
  constructor(name){
    this.name=name||'alex'  //实例属性创建,赋值
  }
  show(){//实例方法
    console.log('show',this.name);
  }
}
Person2223.VERSION='1.2.3';//静态属性|类属性

//子类
class Worker123 extends Person2223{
  constructor(name,job){
    super(name);//类如果有继承 super就要出现
    this.job=job||'卖烧饼';
  }
  show2(){
    console.log(this.job,this.name);
  }
}

es6+

//es7 类
class Person123{
  name='alex'; //实例属性  放在类内部,设置默认值
  age; //没有默认值的实例属性
  static VER='1.11.1';  //类属性 静态属性
  constructor(name,age){
    this.name=name;
    this.age=age||20; //构造器里面可以初始化实例属性
  }

  show(){//方法
    console.log(this.name,this.age,this.show);//访问实例属性
  }

  static show2(){//静态|类 方法定义
    console.log(this.name)
  }
}

class Workerr321 extends Person123{

  job; //实例属性

  static SUM=100;

  constructor(name,age,job){
    super(name,age);//调用父类 影响父类传入到当前的实例属性
    this.job=job||'卖闲鱼'; //构造器初始化
    // this.address='外滩18号';//实例属性,要实现声明
  }

  showJob(){
    console.log(this.job);
  }

}

组件

react组件:类式组件和函数式组件和api式组件

创建组件

//es6
import React from 'react';

class 组件名 extends React.Component{
  
  //每一个组件都会重Component基类上继承props,state,refs,context

  state={} 实例属性 组件状态

  static msg;  类属性

  constrctor(props){ //需要在构造时,修改组件的状态时,constrctor才会出现
    super(props) //类如果有继承 super就要出现
      需要在组件构造器内处理传递过来的props时,props参数就出现

    this.state={ // 本地状态

    }
  }
  render(){
    return jsx|null   //jsx~~要渲染   null不渲染
  }
  方法1(){} 自定义的方法
  static 方法2(){}
}

//es5
var  createReactClass = require('create-react-class');
var Greeting = createReactClass({
  getDefaultProps: function() {
    return {
      name: 'Mary'
    };
  },
  getInitialState: function() {
    return {count: this.props.name||0};
  },
  render: function() {
    // return <h1>Hello, {this.props.name}/{this.state.count}</h1>;
    return React.createElement('h1',{},`Hello, ${this.props.name}/${this.state.count}`)
  }
});

//函数式
function 组件名(){
  return jsx|null   //jsx~~要渲染   null不渲染
}

使用组件

//声明式
<App/>
<Header></Header>

//编程式
new 组件名({props})
组件名({props})

渲染(描画)页面

import ReactDom from 'react-dom';
var RactDom = require('react-dom');
ReactDom.render(jsx,插入点,回调)

props

传递属性

<组件名 属性名=值 属性名2=值2 .. />

propName=“字符” propName={js数据类型}

使用属性

{this.props.属性名}

this 代表的是组件本身

对象无法直接通过{对象}展示

类型检查

import propsTypes from 'prop-types'

//默认值:		
组件.defaultProps={propName:值,xx:oo}

//类型约定:
组件.propTypes={propsName:propsTypes库.类型名,xx:类型}
//propsTypes库.array/bool/func/number/object/string

//必传参数
propName: propsTypes库.类型名.isRequired

组件无论是使用函数声明还是通过 class 声明,都决不要修改自身的 props

事件

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
  • 类组件,事件函数内部this会丢失

事件绑定

<JSX元素 onClick={this.实例方法|函数体}

修正this

onClick={this.方法.bind(this,值)}
onClick={()=>this.方法()}
构造器: this.方法=this.方法.bind(this)  √
实例方法=()=>{箭头函数定义方法}  √√

事件对象

实例方法(ev)	ev 代理事件对象 ev.target 返回虚拟Vdom √

冒泡

阻止: ev.stopPropagation()

默认行为

阻止: ev.preventDefault()

传参

onClick={this.clickHandler2.bind(this, 12)}
onClick={()=>this.clickHandler2(12)}

组件状态

state|数据|私有状态|本地状态

定义

//es6+ 
//实例属性: state    
class App{state:{}}

//es6:构造器 this.state  
class App extends React.Component{
  constructor(){
    this.state={}
  }
}

//ES5:
createReactClass({
  getInitialState:function(){
    return {
      状态名:值,xx:oo
    }
  }   
})

获取

//渲染
{this.state.proname}
//获取
this.state.proname

修改状态

//修改
this.setState(对象)  //浅合并state

this.setState((asyncState,prevProps)=>{
  //一般是用于在setState之前做一些操作
  //this.state==同步结果
  //asyncState==异步结果
  //this.props==后
  //prevProps==前
  return {
    sname:value
  }
}) 

this.setState({
  sname:value
}, () => {
  //一般是用于在setState之后做一些操作
  //this.state == 修改之后的state
})

setState的结果是异步的

列表渲染

//对象 数组 string 数字
this.props|state.属性名.map(function(val,index){
  return jsx
})

条件渲染

//表达式渲染
this.state|props.proname ? jsx1 : jsx2
this.state|props.proname && jsx

//render里面写语句
render(){
  let el=null;
  if(this.state|props.proname){
    el=jsx1
  }else{
    el=jsx2
  }
  
  return el
}

//渲染写成实例方法
renderFn(参数){
  ...参数 做判断
  return el
}
render(){
  return {this.renderFn(条件)}
}

refs

需要抓取dom元素与第三方 DOM 库集成,触发命令式动画,管理焦点,文本选择或媒体播放

用法

refs用法 有4种

//1、 string refs
<jsx元素 ref="名字"...
this.refs.名字

//2. 实例化
this.firstRef = React.createRef() //发生在类内 | 构造器
<jsx ref={this.firstRef} />
  
this.firstRef 访问 -》 {current:dom}
  
// 3. callback refs  回调 √
<jsx ref={el => this.定义一个实例属性 = el}
this.定义一个实例属性 //后期用作访问元素本身

// 4. 转发 refs
  //引用编译后的组件内部的元素,要被引用的组件是一个函数式组件
  
<包装后的子组件 ref={el=>this.inputRef=el} />
  
//子组件是个函数式
const 包装后的子组件 = React.forwardRef((props, ref) => (
  ...
  <input type="text"  ref={ref}/>)
  ...                       
));
  
//当组件挂载时,将 DOM el元素传递给 ref 的回调
//当组件卸载时,则会传递 null。
//ref 回调会在 componentDidMount 和 componentDidUpdate 生命周期之前调用

受控元素

表单的value受控,受数据控制

value={this.state.数据名}  //model->view
onChange={this.方法}   //view->model

处理多个输入元素

可以为每个元素添加一个 name 属性(通常和数据名一致),处理函数根据 event.target.name 的值来选择要做什么

<input name="inputUserName" 
<input name="inputContent"

this.setState({[ev.target.name]:ev.target.value})

双向绑定

非受控元素

要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以 使用 ref 来从 DOM 节点中获取表单数据

<input type="text" ref="xx" />

默认值

表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新,指定一个 defaultValue 属性,而不是 value

留言板

样式

css

引用

<jsx className="类名 类名2" className={返回字符}
<jsx style={{key:value,key:value}}
//style的属性值,可以不给单位,默认px  子属性小驼峰

定义

  • index.html : 引入 link/style 公共样式 不优化 第三方样式
  • index.jsx: import ‘./css/xx.css’ 是全局 公共样式 会优化
  • 组件.jsx import ‘./css/xx.css’ 全局 希望私有 会优化

选择器冲突解决方案

  • 命名空间 BEM
  • BEM:块(block)、元素(element)、修饰符(modifier)
  • 模块化
import 变量  from './css/xx.module.css' 
<jsx className={变量.类名|id}

//配置1 
//webpack配置 "style-loader!css-loader?modules" | module:true
//问题:所有css都需要模块化使用

//配置2 
//改名xx.css -> xx.module.css 
//需要模块化的才修改,不影响其他非模块化css写法

scss

需要下载安装包:网页: node-sass 软件: dart-sass

yarn add node-sass -S
/*定义scss*/
$bg-color: #399;
.box{
  background: $bg-color;
}
//引入
import 'xx/xx.scss'

//使用
<jsx className="box"

//模块化
import style form xx.module.scss
<xx className={style.box}

引入scss全局变量

  • 局部scss文件内部: @import ‘./全局.scss’
  • webpack配置一次,局部scss内部直接使用
//1. 安装插件 : sass-resources-loader
//2. 配置修改webpack.config.js

{
  test:sassRegex,
  ...
  use: [
    {loader:'style-loader'},
    {loader:'css-loader'}, 
    {loader:'sass-loader'},
    {
      loader: 'sass-resources-loader',
      options:{
        resources:'./src/xx/全局主题.scss'
      }
    }
  ]
}

注意:
loader:‘css-loader?modules’ ?modules 模块化时需要添加
resources 指向作用域在项目环境下

组件拆分规则

组件拆分目标:为了复用

组件如何拆:单一原则

状态应该给谁(状态提升)

  • 尽量给顶层组件(状态提升),->props->子组件
  • 可以从 props(属性) 得到,那么它可能不应该在 state(状态) 中
  • 方法-》操作数据(数据|状态在哪,方法就应该在哪)
  • props取名从组件本身的角度来命名, 而不是它被使用的上下文环境

动画

tansition

transition: .5s ease all;
进度条

AntMotion

官网,是一款蚂蚁金服的动画组件库,支持单元素,css、进出场动画、及文字动画

组件内部的 一级元素&& 做动画
一级元素要有key,根据编号依次做动画,无key不动画,路由离场动画无效
包裹路由组件无效(一级元素&& 进退场)

生命周期

实例化 -> 更新期 -> 销毁时

es5版

实例化

  1. 取得默认属性(getDefaultProps) 外部传入的props
  2. 初始状态(getInitailState) state状态
  3. 即将挂载 componentWillMount
  4. 描画VDOM render
  5. 挂载完毕 componentDidMount

次新版

实例化

  1. 取得默认属性,初始状态在constructor中完成

    运行一次,可读数据,可同步修改state,异步修改state需要setState,setState在实例产生后才可以使用,可以访问到props

  2. 即将挂载 componentWillMount

  3. 描画VDOM render

  4. 挂载完毕 componentDidMount

    使用ref,使用setState,读取数据

更新期

  1. props改变 componentWillReceiveProps(nextProps)
    初始化render时不执行 这里调用更新状态是安全的,并不会触发额外的render调用,nextProps 更新后 this.props更新前,路由监听

  2. 是否更新 shouldComponentUpdate

    指视图 return true/false

  3. 即将更新 componentWillUpdate

  4. 描画dom render

    不要在这里修改数据

  5. 描画结束 componentDidUpdate

销毁时

componentWillUnmount即将卸载,可以做一些组件相关的清理工作,例如取消计时器、网络请求等

所有子挂载完,才标志着父挂载完,父更新子更新,子更新父不更新

新版

脑图,挂载前、更新前、props更新前统一用getDerivedStateFromProps代替,并添加了返回快照钩子getSnapshotBeforeUpdate

返回快照:发生在render完了,但还没有去编译真实dom之前,返回dom的快照

实例化

  1. 渲染前 static getDerivedStateFromProps(nextProps,nextState) {}

    无法访问this
    nextProps,nextState是更新后的
    必须返回 一个对象,用来更新state 或者 返回 null不更新
    必须要初始化state
    场景:state 的值在任何时候都取决于 props时

  2. 渲染中 render

    必须return jsx|string|number|null
    不会直接与浏览器交互:不要操作DOM|和数据

  3. 挂载后 componentDidMount

    使用ref,使用setState,读取数据

更新期

  1. 渲染前 static getDerivedStateFromProps(nextProps, nextState)

  2. 是否渲染 shouldComponentUpdate(nextProps, nextState)

    是否更新,必须返回true/false
    首次渲染或使用 forceUpdate() 时不会调用该方法
    nextProps,nextState更新后的,this.props,this.state 更新前的
    return false 只阻止当前组件渲染

  3. 渲染中 render

  4. dom快照 getSnapshotBeforeUpdate(prevProps, prevState)

    组件能在发生更改之前从 DOM 中捕获一些信息(dom渲染前的状态)
    返回的 值|null 会给 componentDidUpdate
    prevProps, prevState 更新前 this.props,this.state更新后

    事例

  5. 更新后 componentDidUpdate(prevProps, prevState,snopshot)

    this.props.更新后的
    snopshot 是 getSnapshotBeforeUpdate构造的返回值

    抓取到的是渲染后的dom状态,通过snopshot拿到dom渲染前的状态

销毁时

即将卸载 componentWillUnmount

数据交互

fetch

js原生api,是promise的语法糖,用法如下

fetch(url+get数据,{配置}).then((res)=>{}).catch((err)=>{})

//配置
//method:'POST'  默认get
//headers:{"Content-type":"application/x-www-form-urlencoded"},
//body:'a=1&b=2'|URLSearchParams
//注意: body数据为字符时,需要携带请求头
//async + await 用法

res.ok : true/false 成功/失败
res.status: 状态码
res.body : 数据 数据流(stream)
res.text() : 转换 文本(string),过程异步,return res.text()
res.json() : 转 对象

文档

jsonp

fetch不带jsonp请求 需要依赖第三库yarn add fetch-jsonp --save

import fetchJsonp from 'fetch-jsonp'

fetchJsonp(url+数据,{配置}).then((res)=>{}).catch(err=>{})

//是个promise 返回promise 数据是个流
//res.json()  -> 流转换数据 是异步

timeout: 延时 5000 配置
jsonpCallback: 回调函数key callback
jsonpCallbackFunction: null

百度下拉(函数节流、事件、setState异步)

axios

同vue

umi-request

文档

客户端代理

正向代理隐藏真实客户端,反向代理隐藏真实服务端,正向代理实现翻墙,反向代理实现跨域,客户端代理指的就是代码写在客户端,不过实现的是跨域

方案1

//配置: package.json
"proxy":"https://uncle9.top"

//组件
/api/xx ...

问题: 只能代理一个服务器

方案2

利用客户端代理中间件(http-proxy-middleware)完成, 官网给了新的使用方式,在src下新建文件setupProxy.js加下面代码,无需单独应用,webpack会自动引入文件。

// src/ 创建 setupProxy.js

//verion < 1.0
const proxy = require('http-proxy-middleware'); //需要安装中间件  
module.exports = function(app) {
  app.use(
    proxy("/api", {
      target: 'https://uncle9.top',
      changeOrigin: true
    })
  );
  app.use(
    proxy("/v2", {
      target: "https://api.douban.com",
      changeOrigin: true
    })
  );
};

//组件: /api/xx ... | /v2/...

//verion > 1.0
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {

  app.use('/api', createProxyMiddleware({
    target: 'http://localhost:3001',
    changeOrigin: true,
  }));

  app.use('/api2', createProxyMiddleware({
    target: 'http://vareyoung.top',
    changeOrigin: true,
    pathRewrite: { //路径替换
      '^/api2': '/api', // axios 访问/api2 == target + /api
    }
  }));

};

方案3

配置create-react-app环境下的webpack

// config/webpackDevServer.js

 proxy: { //代理是从指定的target后面开始匹配的,不是任意位置;配置pathRewrite可以做替换
      '/api': { //axios访问 /api ==  target + /api
        target: 'http://localhost:3001',
        changeOrigin: true, //创建虚拟服务器 
        ws: true, //websocket代理
      },
      '/douban': { // axios 访问 /douban == target + '/douban' +  '/v2/movie/in_theaters?start=0&count=10'
        target: 'https://douban.uieee.com',
        changeOrigin: true,
        pathRewrite: { //路径替换
          '^/douban': '', // axios 访问/douban/v2 == target + /v2
        }
      }
    }
  }

mock

JSON-Server 是一个 Node 模块,运行 Express 服务器,你可以指定一个 json 文件作为 api 的数据源。

安装json-server

yarn add json-server -S

启动 json-server

json-server可以直接把一个json文件托管成一个具备全RESTful风格的API,并支持跨域、jsonp、路由订制、数据快照保存等功能的 web 服务器。

db.json文件的内容:

{
  "course": [
    {
      "id": 1000,
      "course_name": "马连白米且",
      "autor": "袁明",
      "college": "金并即总变史",
      "category_Id": 2
    },
    {
      "id": 1001,
      "course_name": "公拉农题队始果动",
      "autor": "高丽",
      "college": "先了队叫及便",
      "category_Id": 2
    }
  ]
}

例如以下命令,把db.json文件托管成一个 web 服务。

$ json-server --watch --port 53000 db.json

输出类似以下内容,说明启动成功。

\{^_^}/ hi!

Loading db.json
Done

Resources
http://localhost:53000/course

Home
http://localhost:53000

Type s + enter at any time to create a snapshot of the database
Watching...

此时,你可以打开你的浏览器,然后输入:http://localhost:53000/course

json-server 的相关启动参数

  • 语法:json-server [options] <source>
  • 选项列表:
参数简写默认值说明
–config-c指定配置文件[默认值: “json-server.json”]
–port-p设置端口 [默认值: 3000]Number
–host-H设置域 [默认值: “0.0.0.0”]String
–watch-wWatch file(s)是否监听
–routes-r指定自定义路由
–middlewares-m指定中间件 files[数组]
–static-sSet static files directory静态目录,类比:express的静态目录
–readonly–roAllow only GET requests [布尔]
–nocors–ncDisable Cross-Origin Resource Sharing [布尔]
–nogzip, --ng Disable GZIP Content-Encoding [布尔]
–snapshots-SSet snapshots directory [默认值: “.”]
–delay-dAdd delay to responses (ms)
–id-iSet database id property (e.g. _id) [默认值: “id”]
–foreignKeySuffixfks Set foreign key suffix (e.g. _id as in post_id)[默认值: “Id”]
–help-h显示帮助信息[布尔]
–version-v显示版本号[布尔]
  • source可以是json文件或者js文件。实例:
json-server --watch -c ./jsonserver.json
json-server --watch db.js  命令行里面要的db是个函数
json-server db.json
json-server --watch -port 8888 db.json

动态生成模拟数据

启动json-server的命令:json-server --watch db.js 是把一个js文件返回的数据托管成web服务。

db.js配合mockjs库可以很方便的进行生成模拟数据。

// 用mockjs模拟生成数据
var Mock = require('mockjs');

module.exports = () => {
  // 使用 Mock
  var data = Mock.mock({
    'course|227': [
      {
        // 属性 id 是一个自增数,起始值为 1,每次增 1
        'id|+1': 1000,
        course_name: '@ctitle(5,10)',
        autor: '@cname',
        college: '@ctitle(6)',
        'category_Id|1-6': 1
      }
    ],
    'course_category|6': [
      {
        "id|+1": 1,
        "pid": -1,
        cName: '@ctitle(4)'
      }
    ]
  });
  // 返回的data会作为json-server的数据
  return data;
};

路由

默认的路由

json-server为提供了GET,POST, PUT, PATCH ,DELETE等请求的API,分别对应数据中的所有类型的实体。

# 获取所有的课程信息
GET    /course

# 获取id=1001的课程信息
GET    /course/1001

# 添加课程信息,请求body中必须包含course的属性数据,json-server自动保存。
POST   /course

# 修改课程,请求body中必须包含course的属性数据
PUT    /course/1
PATCH  /course/1

# 删除课程信息
DELETE /course/1

# 获取具体课程信息id=1001
GET    /course/1001

自定义路由

当然你可以自定义路由:

$ json-server --watch --routes route.json db.json

route.json文件

{
  "/api/*": "/$1",    //   /api/course   <==>  /course
  "/:resource/:id/show": "/:resource/:id",
  "/posts/:category": "/posts?category=:category",
  "/articles\\?id=:id": "/posts/:id"
}

自定义配置文件

通过命令行配置路由、数据文件、监控等会让命令变的很长,而且容易敲错,可以把命令写到npm的scripts中,但是依然配置不方便。

json-server允许我们把所有的配置放到一个配置文件中,这个配置文件默认json-server.json;

例如:

{
  "port": 53000,
  "watch": true,
  "static": "./public",
  "read-only": false,
  "no-cors": false,
  "no-gzip": false,
  "routes": "route.json"
}

使用配置文件启动json-server:

# 默认使用:json-server.json配置文件
$ json-server db.js  
$ json-server db.json 

# 指定配置文件
$ json-server --watch -c jserver.json db.json

过滤查询

查询数据,可以额外提供

GET /posts?title=json-server&author=typicode
GET /posts?id=1&id=2

# 可以用 . 访问更深层的属性。
GET /comments?author.name=typicode

还可以使用一些判断条件作为过滤查询的辅助。

GET /posts?views_gte=10&views_lte=20

可以用的拼接条件为:

  • _gte : 大于等于
  • _lte : 小于等于
  • _ne : 不等于
  • _like : 包含
GET /posts?id_ne=1
GET /posts?id_lte=100
GET /posts?title_like=server

分页查询

默认后台处理分页参数为: _page 第几页, _limit一页多少条。

GET /posts?_page=7
GET /posts?_page=7&_limit=20

默认一页10条。

后台会返回总条数,总条数的数据在响应头:X-Total-Count中。

排序

  • 参数: _sort设定排序的字段
  • 参数: _order设定排序的方式(默认升序)
GET /posts?_sort=views&_order=asc
GET /posts/1/comments?_sort=votes&_order=asc

支持多个字段排序:

GET /posts?_sort=user,views&_order=desc,asc

任意切片数据

GET /posts?_start=20&_end=30
GET /posts/1/comments?_start=20&_end=30
GET /posts/1/comments?_start=20&_limit=10

全文检索

可以通过q参数进行全文检索,例如:GET /posts?q=internet

实体关联

关联子实体

包含children的对象, 添加_embed

GET /posts?_embed=comments
GET /posts/1?_embed=comments
关联父实体

包含 parent 的对象, 添加_expand

GET /comments?_expand=post
GET /comments/1?_expand=post

其他高级用法

json-server本身就是依赖express开发而来,可以进行深度定制。细节就不展开,具体详情请参考官网

const jsonServer = require('json-server');//在node里面使用json-server包
const db = require('./db.js');//引入mockjs配置模块
const path = require('path');
const Mock = require('mockjs');
let mock='/mock';//定义路由根别名

//创建服务器
const server = jsonServer.create();//创建jsonserver 服务对象


//配置jsonserver服务器 中间件
server.use(jsonServer.defaults({
  static:path.join(__dirname, '/public'),//静态资源托管
}));
server.use(jsonServer.bodyParser);//抓取body数据使用json-server中间件


//响应
server.use((request, res, next) => {//可选 统一修改请求方式
  // console.log(1)
  // request.method = 'GET';
  next();
});

//登录注册校验
let mr = Mock.Random;//提取mock的随机对象
server.get(mock+'/login', (req, res) => {
  // console.log(req.query, req.body);//抓取提交过来的query和body
  let username=req.query.username;
  let password=req.query.password;
  (username === 'aa' && password === 'aa123')?
    res.jsonp({
      "err": 0,
      "msg": "登录成功",
      "data": {
        "follow": mr.integer(1,5),
        "fans": mr.integer(1,5),
        "nikename": mr.cname(),
        "icon": mr.image('20x20',mr.color(),mr.cword(1)),
        "time": mr.integer(13,13)
      }
    }) :
    res.jsonp({
      "err": 1,
      "msg": "登录失败",
    })

});
server.post(mock+'/reg', (req, res) => {
  let username=req.body.username;
  (username !== 'aa') ?
    res.jsonp({
      "err": 0,
      "msg": "注册成功",
      "data": {
        "follow": mr.integer(0,0),
        "fans": mr.integer(0,0),
        "nikename": mr.cname(),
        "icon": mr.image('20x20',mr.color(),mr.cword(1)),
        "time": mr.integer(13,13)
      }
    }) :
    res.jsonp({
      "err": 1,
      "msg": "注册失败",
    })

});

//响应mock接口 自定义返回结构 定义mock接口别名
const router = jsonServer.router(db);//创建路由对象 db为mock接口路由配置  db==object

router.render = (req, res) => {//自定义返回结构
  let len = Object.keys(res.locals.data).length; //判断数据是不是空数组和空对象
  // console.log(len);

  setTimeout(()=>{//模拟服务器延时
    res.jsonp({
      err: len !== 0 ? 0 : 1,
      msg: len !== 0 ? '成功' : '失败',
      data: res.locals.data
    })
  },1000)

  // res.jsonp(res.locals.data)

};

server.use(jsonServer.rewriter({//路由自定义别名
  [mock+"/*"]: "/$1",

  // "/product\\?dataName=:dataName": "/:dataName",
  // "/banner\\?dataName=:dataName": "/:dataName",
  // "/detail\\?dataName=:dataName&id=:id": "/:dataName/:id",

  // "/product/del\\?dataName=:dataName&id=:id": "/:dataName/:id",
  // "/product/add\\?dataName=:dataName": "/:dataName",
  // "/product/check\\?dataName=:dataName&id=:id": "/:dataName/:id"
}));

server.use(router);//路由响应



//开启jsonserver服务
server.listen(3333, () => {
  console.log('mock server is running')
});

单页VS多页

页面模式多页面模式(MPA Multi-page Application)单页面模式(SPA Single-page Application)
页面组成多个完整页面, 例如page1.html、page2.html等由一个初始页面和多个页面模块组成, 例如:index.html
公共文件加载跳转页面前后,js/css/img等公用文件重新加载js/css/img等公用文件只在加载初始页面时加载,更换页面内容前后无需重新加载
页面跳转/内容更新页面通过window.location.href = "./page2.html"跳转通过使用js方法,append/remove或者show/hide等方式来进行页面内容的更换
数据的传递可以使用路径携带数据传递的方式,例如:http://index.html?account=“123”&password=123456"",或者localstorage、cookie等存储方式直接通过参数传递,或者全局变量的方式进行,因为都是在一个页面的脚本环境下
用户体验如果页面加载的文件相对较大(多),页面切换加载会很慢页面片段间切换较快,用户体验好,因为初次已经加载好相关文件。但是初次加载页面时需要调整优化,因为加载文件较多
场景适用于高度追求高度支持搜索引擎的应用高要求的体验度,追求界面流畅的应用
转场动画不容易实现容易实现

单页面模式:相对比较有优势,无论在用户体验还是页面切换的数据传递、页面切换动画,都可以有比较大的操作空间 多页面模式:比较适用于页面跳转较少,数据传递较少的项目中开发,否则使用cookie,localstorage进行数据传递,是一件很可怕而又不稳定的无奈选择

路由

官网 中文

vue-routerreact-router
配置分离式(统一位置配置)嵌套式(路由配置在组件内部)
匹配排他性(只有一个路由被渲染)包容性(多路由渲染)
形态静态路由动态路由

理念

遵循Just Component的 API 设计理念 万物皆组件,路由规则位于布局和 UI 本身之间

安装

React Router被拆分成三个包:react-router,react-router-dom和react-router-native。react-router提供核心的路由组件与函数。其余两个则提供运行环境(即浏览器与react-native)所需的特定组件

yarn add react-router-dom --save

提供组件

组件作用
BrowserRouter约定模式 为 history,使用 HTML5 提供的 history API 来保持 UI 和 URL 的同步
HashRouter约定模式 为 hash,使用 URL 的 hash (例如:window.location.hash) 来保持 UI 和URL 的同步
NavLink声明式跳转 还可以约定 路由激活状态
Link声明式跳转 ~~ push 无激活状态
Redirect重定向 ~~ replace
Route匹配、展示
Switch排他性匹配
Prompt后置守卫
withRouter把不是通过路由切换过来的组件中,将 history、location、match 三个对象传入props对象上

结构

  • BrowserRouter|HashRouter
    • 根组件(App)
      • NavLink|Link
      • Route
      • Redirect
        • 子组件
          • NavLink|Link
          • Route

BrowserRouter

属性类型作用
basenamestring所有位置的基本URL。如果您的应用是从服务器上的子目录提供的,则需要将其设置为子目录。格式正确的基本名称应以斜杠开头,但不能以斜杠结尾
getUserConfirmationFunction用于确认导航的功能。默认使用window.confirm
forceRefreshboolean是否调整时强制刷新,模拟旧式服务器渲染

Route

属性类型作用
pathstring | string[]路由匹配路径。没有path属性的Route 总是会 匹配
exactboolean为true时,要求全路径匹配(/home)。路由默认为“包含”的(/和/home都匹配),这意味着多个 Route 可以同时进行匹配和渲染
componentFunction ReactElement在地址匹配的时候React的组件才会被渲染,route props也会随着一起被渲染
renderFunction内联渲染和包装组件,要求要返回目标组件的调用

Link

属性类型作用
tostring | {pathname,search,hash}要跳转的路径或地址
replaceboolean是否替换历史记录

NavLink

属性类型作用
tostring object要跳转的路径或地址
replaceboolean是否替换历史记录
activeClassNamestring当元素被选中时,设置选中样式,默认值为 active
activeStyleobject当元素被选中时,设置选中样式
exactboolean严格匹配

Switch

该组件用来渲染匹配地址的第一个Route或者Redirect,仅渲染一个路由,排他性路由,默认全匹配(场景:侧边栏和面包屑,引导选项卡等

属性类型作用
locationstring object
childrennode

Redirect

将导航到新位置。新位置将覆盖历史记录的当前位置

属性类型作用
fromstring来自
tostring object去向
pushboolean添加历史记录
exactboolean严格匹配
sensitiveboolean区分大小写

404

<Route component={Error}/> 总是会匹配

参数数据传递

let {history,location,match}=props
<Link to={match.url+'/001'}/>
<Link to={`${match.url}/002?a=1&b=2`}/>
<Link to={{pathname:match.url+'/003',search:'?a=11&b=12'}}

<Route path={match.path+'/:aid'} component={Detail}

url - (浏览器 URL 中的实际路径) URL 匹配的部分。 用于构建嵌套的
path - (路由编写的路径) 用于匹配路径模式。用于构建嵌套的

接收

//接参数:
{match.params.aid}
//接数据
{location.search}
//接地址:
{location.pathname}

无法从v4+ 中获取 URL 的查询字符串了。因为没有关于如何处理复杂查询字符串的标准。所以,作者让开发者去选择如何处理查询字符串。推荐qs库|query-string

编程式跳转

history.push('/user?a=1&b=2')
history.push({pathname:'/user',search:'?a=11&b=22'})
history.replace({pathname:'/user',search:'?a=111&b=222'})
history.go(-1)

非路由跳转组件

不是所有组件会通过路由跳转,也需要抓取路由上下文时,解决方案

  1. 通过路由跳转 <Route component={丢失上下文的组件}
  2. 通过属性传递 <丢失上下文的组件 {…this.props} this==父组件
  3. 通过withRouter包装
import {withRouter} from 'react-router-dom'
class 组件 extends Component{}
export default withRouter(组件)

前置授权路由

需要自定义路由,具体为,自定义一个组件,代替Route,其内部根据条件返回一个Route 组件指向目标组件,或者Route的render函数内部判断加载目标,最后组件树关系为:switch>自定义组件>Route>目标组件

<Auth path="/goods" component={Goods} />
<Auth path="/user" component={User} />

export default class Auth extends React.Component{

  state={
    hasSendAuth:false,//是否发送过介权请求
    auth:false,//介权是否通过
    data:{}//预载数据
  };

  async componentDidMount(){
    let res = await axios({url:'/data/user.json'})
    console.log('数据回来了')
    this.setState({
      auth:res.data.auth,
      hasSendAuth:true,
      data:res.data.data
    })
  }

  render(){
    // console.log('渲染了',this.props)  //包含了path,component的一个传入
    let {component:Component} = this.props;//目标组件
    if (!this.state.hasSendAuth) return null;

    return <Route render={props=>(//...props 目标组件需要用到的路由信息
      this.state.auth ?
        <Component {...props} data={this.state.data} /> :// 数据预载
        <Redirect to="/login" />
    )}/>

  }
}

后置守卫

// reg.jsx
import { Prompt } from 'react-router-dom'
<Prompt
  when={this.state.isBlocking}
  message={location=>...}
/>

message: 后面可以跟简单的提示语,也可以跟函数,函数是有默认参数的。
when: when的属性值为true时防止跳转;

单页滚动条

路由切换,每次切换到页面顶部

static getDerivedStateFromProps(nextProps){//props改变时
  if(this.props.location !== nextProps.location){//当前地址不等于目标地址
    window.scrollTo(0,0);//滚动到顶部
  }
}

页面切换出去再切换回来后怎样保持之前的滚动位置

//sTop =  模块内部变量 | 类属性
componentDidMount(){
  window.scrollTo(0,sTop)
}
componentWillUnmount(){
  sTop = document.documentElement.scrollTop
}			

项目

技术栈选型

前端

create-react-app

react-router-dom

axios

redux/react-redux/react-think

mockjs/json-server

prop-types

qs|query-string

后端

nodejs

express

mongodb

bcrypt

jsonwebtoken

multer

环境规划

|-config	 CRA配置
|-scirpts  CRA配置
|-pubilc
  |- data
    |- 静态数据
  |-index.html 浏览器入口
|-node_modules
|-mock 数据模拟
	|- public
  |-db.js
  |-app.js
|-src
  |-library 公司内部库
    |-jquery.js
    |-swiper.js
  |-utils 工具包
    |-date.js / fillzero.js/...
  |-layouts 布局
    |- Default/Header/Footer
  |-components 应用内部基础通用组件、木偶组件
    |- swiper、input、loading
    |- cell、uc-nav
    |- button
  |-pages  智能组件 页面
    |- Home / Follow / Column / User
    |- Detail / Login / Reg / Error
	|-guard
			守卫组件
  |- assets
    |- img
    |- css、sass
    |- font
  |- store
    |- state/reducer/asyncAction
	|- plugins
		|- axios /  ....
  Index.js

组件开发

准备工作

移动端(设置视口,设置字体比例,基础样式normal,base)

资源引入

  • index.html引入 不优化
  • index.js 引入 优化
  • 组件 引入 优化

资源指向

相对路径 以src为根静态资源,绝对路径 以public为根动态资源, jsx前景图片默认都指向public, jsx里面行间样式链接图片资源指向了pubic,

布局方案

  • 切图,需要设计稿,用户端开发时用到
  • UI库,管理端开发时用到,常用的UI库(elementUI/ant.design)
  • 模板移植,老项目重构时用到

数据交互

客户端代理

module.exports = function(app) {

  app.use('/api', createProxyMiddleware({
    target: 'http://localhost:3001',
    changeOrigin: true,
  }));

  app.use('/api2', createProxyMiddleware({
    target: 'http://vareyoung.top',
    changeOrigin: true,
    pathRewrite: { //路径替换
      '^/api2': '/api', // axios 访问/api2 == target + /api
    }
  }));

};

拦截器axios

import React from 'react';
import axios from 'axios';
import {BrowserRouter as Router} from 'react-router-dom'
import {baseLocalUrl} from '../server'
import qs from 'qs'

// 添加一个请求的拦截
axios.interceptors.request.use((config) => {
  //1抓取本地token,携带在请求头里
  let user = window.localStorage.getItem('user');
  user = user ? qs.parse(user) : '';
  config.headers={'token': user.token}

  //显示loading...

  return config;//2返回请求

}, function(error) {
  // 请求错误时做点事
  return Promise.reject(error);
});

//添加一个响应拦截
axios.interceptors.response.use(function(response) {
  console.log('响应拦截',response);
  
  let router=new Router();
  //token过期: 返回值2,当前路由不是login时跳转 
  if (response.data.err === 2 && !router.history.location.pathname.includes('/login')) {
    console.log('token 失败 跳转到login',router);
    window.location.href=baseLocalUrl+'/login?path='+router.history.location.pathname

    /*router.history.push({  //hash 模式可以,history模式有问题
      pathname: '/login',
      search: "path="+router.history.location.pathname
    })*/

  }
  return response;

}, function(error) {

  return Promise.reject(error);
});

React.axios = axios;//axios绑到对象包上
React.Component.prototype.axios = axios; // axios绑定到Component类的原型   组件|this.axios
window.axios = axios;  //×   希望全局使用axios , 使用webpack 来配置
export default axios;

拦截器umi-request

import React from 'react';
import {BrowserRouter as Router} from 'react-router-dom'
import request,{ extend } from 'umi-request';
import qs from 'qs'

// request拦截器, 改变url 或 options.
request.interceptors.request.use((url, options) => {
  
  //1抓取本地token,携带在请求头里
  let user = window.localStorage.getItem('user');
  user = user ? qs.parse(user) : '';
  options.headers={'token': user.token}
  
  return (
    {
      url,
      options
    }
  );
});

// 提前对响应做异常处理
request.interceptors.response.use(async (response) => {
  
  const codeMaps = {
    200: '服务器成功返回请求的数据。',
    201: '新建或修改数据成功。',
    202: '一个请求已经进入后台排队(异步任务)。',
    204: '删除数据成功。',
    400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
    401: '用户没有权限(令牌、用户名、密码错误)。',
    403: '用户得到授权,但是访问是被禁止的。',
    404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
    406: '请求的格式不可得。',
    410: '请求的资源被永久删除,且不会再得到的。',
    422: '当创建一个对象时,发生一个验证错误。',
    500: '服务器发生错误,请检查服务器。',
    502: '网关错误。',
    503: '服务不可用,服务器暂时过载或维护。',
    504: '网关超时。',
  };
  
  console.log(codeMaps[response.status]);
  
	const data = await response.clone().json();//克隆响应对象做解析处理
  
  let router=new Router();
  //token过期: 返回值2,当前路由不是login时跳转 
  if (data.err === 2 && !router.history.location.pathname.includes('/login')) {
    console.log('token 失败 跳转到login',router);
    window.location.href=baseLocalUrl+'/login?path='+router.history.location.pathname

    /*router.history.push({  //hash 模式可以,history模式有问题
      pathname: '/login',
      search: "path="+router.history.location.pathname
    })*/
  }
  
  return response;
});


React.request = request;//request绑到对象包上
React.Component.prototype.request = request; // request绑定到Component类的原型   组件|this.request
window.request = request;  //×   希望全局使用request , 使用webpack 来配置
export default request;

服务封装api

export const queryDetail = async ({id, apiname}) => {
  return axios({
    url: `/mock/${apiname}/${id}`
  })
};

export const queryAll = async (arr) => {
  return axios.all(arr).then(axios.spread((banner, home)=>{
    return {home, banner}
  }))
};

export const queryReg = async ({username,password,icon}) => {

  let params = new URLSearchParams();
  params.append("username",username);
  params.append("password",password);

  // await checkUserInput(username,password,icon)
  //icon input:file
  if (icon.files.length>0){
    params.append("icon",icon.files[0]);
  }

  return axios({
    url:'/mock/reg',
    method:'post',
    data: params//携带数据 对象  字符  URLSearchParams
  });
};

登录

//更新同步localStrage
window.localStorage.setItem('user',qs.stingify(...))
//跳转到之前
history.push({
  pathname:qs.parse(
    this.props.location.search,{ 
    ignoreQueryPrefix:true
  }).path
})

列表、详情

home-> cell/swiper -> detail 拿到id dataName

<jsx dangerouslySetInnerHTML={{__html:HTML字符的数据}}></jsx>危险数据的信任和转换

全局方法过滤

|-common|utils
  date.js
  fillzero.js
  ...
  index.js
    import date/fillzero ..
    export {
      date,fillzero
    }

公共数据

//路由检测: pathname的变化

static getDerivedStateFromProps(nextProps,nextState){

  let path = nextProps.location.pathname;

  if (/home|follow|column/.test(path)){
    return {bNav:true,bFoot:true}
  }
  if (/detail|login|reg/.test(path)){
    return {bNav:false,bFoot:false}
  }
  if (/user/.test(path)){
    return {bNav:false,bFoot:true}
  }

  return null;

}

//loading数据 
//订阅发布库
//订阅发布库: App订阅,  组件求数据时发布 | 拦截器发布

pubsub-js

安装

 yarn add pubsub-js -S

订阅

token = PubSub.subscribe('事件名称', 函数(msg,data));
//msg == 事件名称
//data == 传入的数据

发布

PubSub.publish('事件名称', '数据')

取消订阅

PubSub.unsubscribe(token);  //取消指定订阅
PubSub.clearAllSubscriptions(); //取消所有订阅 不推荐使用

先订阅,再发布,组件卸载时,移除订阅

部署

react的项目打包(dist),拷贝到空node项目环境(public)下,利用node做后端代理,访问json-server服务器的数据(mock),再一同拷贝到购买的云服务器上,阿里云的服务器类型选择centos

前端代理端服务端
reactnodejson-server + mock | 第三方接口服务|自行开发node
js/html/css提供静态资源|代理提供api和库的动态请求

node做代理

方案1

// node项目环境 下安装 http-proxy-middleware 中间件
npm i http-proxy-middleware --save

// app.js
const { createProxyMiddleware } = require('http-proxy-middleware');

//因为 bodyParser 导致的代理转发带有 body 数据的 post 请求会失败,代理中加上把解析后的 body 数据再转回来即可
var restream = function(proxyReq, req) {
  if (req.body) {
      let bodyData = JSON.stringify(req.body);
      // incase if content-type is application/x-www-form-urlencoded -> we need to change to application/json
      proxyReq.setHeader('Content-Type','application/json');
      proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
      // stream the content
      proxyReq.write(bodyData);
  }
}

//响应mock请求,交由中间件转发
app.use('/mock', createProxyMiddleware({
  target: 'http://localhost:3333',
  changeOrigin: true,
  secure: false,
  onProxyReq: restream
}));

方案2

// node项目环境 下安装 express-http-proxy 中间件
npm i express-http-proxy --save

// app.js
const proxy = require('express-http-proxy');

//配置
let opts = {
  preserveHostHdr: true,
  reqAsBuffer: true,
  //转发之前触发该方法
  proxyReqPathResolver: function(req, res) {
      //这个代理会把匹配到的url(下面的 ‘/api’等)去掉,转发过去直接404,这里手动加回来,
      req.url = req.baseUrl+req.url;
      return require('url').parse(req.url).path;
  },
}

//响应mock请求,交由中间件转发
app.use('/mock',proxy('http://localhost:3333',opts));

json-server服务器:三目有问题,压缩分号 ***

无法携带multer文件体,***

nginx代理

扩展

阿里云部署

简洁型部署

买服务器(机器)

  • 选择云服务器ECS、centos系统学生特惠地址
  • 支付宝-》注册-》实名认证填写身份证的信息-》ecs
  • 重设密码初始化磁盘:ecs服务器->控制台

使用finalShell连接服务器

  • 安装 finalShell
  • 启动 finalShell-》新建会话-》SSH链接->主机:公网IP-》端口 : 22-》用户名:root-》密码: 登录密码

給服务器安装环境

//安装node 在 finalShell里面

curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
yum install -y nodejs
检测: node -v

上传代码

  • react 打包: yarn build -> build目录

    创建空的node环境: express -e .

  • build里面的文件 copy -> node 的 public下面

  • 把node项目 -》 拖拽到 finalSheel/usr/local/创建目录/

  • //让阿里云支持node里面的3000端口
    找到控制台->安全组-》配置规则-》添加规则-》端口范围(3000/3000,授权对象(0.0.0.0/0)
    
  • finalShell 里面-> cd /usr/local/你的目录 -> npm start
    

    测试: 浏览器输入: http://公网IP:3000

高要求部署

买服务器(机器)

  • 成人特惠地址,认准云服务器ECS/centos系统
  • 支付宝-》注册-》实名认证填写身份证的信息-》ecs
  • 手动停止服务器 ----> 初始化磁盘 —> 重设密码(登录密码)

使用finalShell连接服务器

  • 安装 finalShell
  • 启动 finalShell-》新建会话-》主机:公网IP-》端口 : 22-》用户名:root-》密码: 登录密码

給服务器安装环境

curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
sudo yum install -y nodejs
检测: node -v

上传代码

  • react 打包: npm run build -> build
  • 本地测试生产环境(css有时打包后有出错)
npm i serve -g
serve -s build -l 8080
问题 : 生产环境下 不能访问 3001
原因  : 生产环境下客户端代理是无效的,部署后的代码需要在服务端做代理
解决: 服务器端 安装ngnix  来完成代理
  • 拷贝 build -> node的public下面 + 本地测试(启动node服务)
  • 整合好的node 拖到 finalShell 下面(不拽node_modules)
npm i 
npm start

給服务器安装json-server服务

//1 copy react下面的mock 到服务器其他目录
|-app.js
|-db.js
|-public
//2 安装依赖
npm init
npm i mockjs json-server
//3 开一个3333安全组(防火墙)
问题汇总

关闭finalShell ,服务断了

//安装pm2, nodejs服务器管理器
npm i pm2 -g

//启动服务器:
pm2 start 启动文件.js 

//浏览器访问项目即可
http://公网IP:node端口

//如果想停掉服务器: 
pm2 stop all

pm2使用

可以有多个app?使用一个实例?

分析:app指向不同端口就好了
解决:app指向不同端口,安全组里添加多个端口,pm2 进入到对应服务器位置,逐个启动,如果端口重复,先启用的应用会占用端口

不想要端口可以?

分析:使用http协议默认的80端口,使用https协议默认端口443
解决: 修改本地的端口号指向80,安全组添加80

不使用ip,使用网址?

分析: 是一个IP和域名关联的过程

解决: 必须得用于一个已经备过案的域名(未备案不可使用一级域名和省略端口),域名购买地址

备案: 特惠专区-》域名与网站->域名新手多重礼(实名,备案15工作日)

域名解析:域名-》解析-》添加记录->记录值(ip)

www:解析后的域名为www.aliyun.com。
@:直接解析主域名 aliyun.com。
二级域名:如:abc.aliyun.com,填写abc。

不备案有什么影响

小程序上线时不能部署,但不影响学习
没有域名不便于宣传,解决:做成二维码
无法使用https安全协议访问

启用https访问

流程:SSL证书->获取https免费证书->配置(node服务器使用https模块响应)

获取https免费证书

下载: 证书通过后->下载 other类型的 xx.key/xx.pem 下载到-> bin/www

配置node:

var https = require('https');
const fs = require('fs');
const port=443;		
app.set('port', port);

const options = {
  key: fs.readFileSync('./bin/1826016_uncle9.top.key'),//指向key
  cert: fs.readFileSync('./bin/1826016_uncle9.top.pem'),
}; 
var server = https.createServer(options,app);//查看nodejs.cn>https模块|或已完成的node项目

安全组规则:添加443 ,443是https的默认端口

在阿里云配置apache+mysql+php

参考资料

历史记录模式路由,强刷找不到

现象:客户端路由服务找/todos/42时,服务器会找/todos/42的接口(没有这个子服务接口)
解决:服务器路由优先,找不到时,返回vue的前端index.html,交还给客户端路由

// node项目 app.js

app.use(function(err, req, res, next) {
	...
  
  if(req.url.includes('/api')){//webApi接口错误
    res.send({
      err:1,
      msg:'不存在的接口名'
    })
  }else if(req.url.includes('/admin')){//服务端Api接口错误
    res.render('error');
  }else{//交还给客户端判断
    res.sendFile(path.join(__dirname, 'public','template', 'index.html'));
  }

});

也可以通过中间件 connect-history-api-fallback 实现

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值