引用
- script src=‘路径’
- M 模型 等同于数据
- V 视图 等同于htmlcss 用户可见的
- VM 视图模型 链接V与M之间的桥梁 传递两者之间的数据
- Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
- 与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。
- Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
- 另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
指令
- v-model:双向数据绑定 视图改变模型也发生改变 模型改变视图也发生改变
<input type="text" v-model="text"> {{text}}
</div>
<script>
new Vue({
data: {
text: '你个小垃圾'
},
el: '.box'
})
</script>
- v-show 控制元素显示或隐藏 true显示 false隐藏
显示display:none/block;
<div class="box">
<input type="checkbox" v-model="text">
<h3 v-show="text">小得瑟</h3>
{{text}}
</div>
<script>
new Vue({
data: {
text: false
},
el: '.box'
})
</script>
-
v-on:事件名=函数名()
@事件名=函数名() (简写形式) -
v-for 遍历
v-for="(v,i) in arr"{{v}…{{i}}}
v代表遍历得每一项;i代表每一项得值;arr代表要遍历的数组; -
v-bind 绑定HTML元素属性
v-bind:属性名=‘表达式’ / 简写 :属性名=‘表达式’ -
v-if=表达式 判断是否加载固定的内容
根据表达式结果的真假,确定是否显示当前元素
true表示加载该元素;false表示不加载该元素
对dom元素进行添加或者是移除 -
v-show与v-if区别:v-if有更高的切换消耗(安全性高)
v-show有更高的初始化的渲染消耗(对安全性无要求选择) -
v-else 必须配合v-if使用否则无效。当v-if条件不成立的时候执行
-
v-else-if当有一项成立时执行
-
v-text 操作网页元素中的纯文本内容。{{}}是他的另一种写法
v-text与{{}}等价,{{}}叫模板插值。v-text叫指令
区别在于渲染的数据比较多的时候,可能会把大括号内容显示出来。俗称屏幕闪动
解决闪动的方法:
1.使用v-text渲染数据
2.使用{{}}语法渲染数据,但同时使用v-cloak指令(用来保持在元素上直到关联实力结束时进行编译),v-cloak并不需要添加到每个标签,只要在el挂载的标签上添加就可以 -
v-html 双大括号会将数据解释为纯文本,而非HTML。
-
v-once 当数据改变时,插值处的内容不会更新(会影响到该节点上的所有属性)
{{text}}
小结
v-show:控制切换一个元素的显示和隐藏
v-on:为 HTML 元素绑定事件监听
v-model:将用户的输入同步到视图上
v-for :遍历 data 中的数据,并在页面进行数据展示
v-bind:绑定 HTML 元素的属性
监听/侦听 watch
- watch:可以监听模型数据 当模型数据改变的时候就会触发
watch:{
监听的data数据(newval,oldval){
console.log(newval+"—"+oldval)
}
}
watch初始化的时候不会运行,只有数据被改变之后才会运行
什么时候使用watch
- 当需要在数据变化时执行异步或开销较大的操作时,watch这个方式是最有用的。
- 计算属性与侦听器的区别:
- 当watch监听的值发生改变就会被调用,watch可以在数据变化时做一些异步处理或者开销大的操作
- 计算属性是计算依赖的值,当依赖的值发生改变才会触发。
交互
- 前后端概念
任何一个应用程序都有前端和后端
前端:客户端,就是浏览器(pc端 手机端)
后端:服务器(性能很好的电脑)是提供计算服务的设备。
服务器的构成包括处理器、硬盘、内存、系统总线等,和通用的计算机架构类似,但是由于需要提供高可靠的服务,
因此在处理能力、稳定性、可靠性、安全性、可扩展性、可管理性等方面要求较高。
Vue请求数据交互
- vue请求数据有Vue-resource、Axios、fetch三种方式。
- Vue-resource是Vue官方提供的插件,axios是第三方插件,fetch es6原生
1.Vue.js resource交互 2.0停止更新了
Vue自身不带处理HTTP请求 如果想使用HTTP请求必须要引入 vue-resource.js 库它可以通过XMLHttpRequest发起请求并处理响应。
也就是说,$.ajax能做的事情,vue-resource插件一样也能做到,而且vue-resource的API更为简洁。
2.Vue.js 交互借助于 $http 完成
下载:npm install --save vue-resource - get 类型
语法:this.$http.get(‘url’,
{params: {key1:val1,key2:val2…}}).
then(function(res){处理请求成功的情况},
function(res){处理请求失败的情况}) - POST类型
语法:this.$http.post(‘url’,
{key1:val1,key2:val2…},
{emulateJSON:true}).
then(function(res){处理请求成功的情况},
function(res){处理请求失败的情况})
Axios
- Axios是第三方插件,不仅能在Vue里使用,还能再其他第三方库中使用例如react
- npm install --save axios
- get类型
语法: axios.get(’/路径?k=v&k=v}
.then((ok)=>{})
.catch((err)=>{}) - post类型
语法:axios.post(’/user’, {k:v,k:v })
.then(function (ok) { })
.catch(function (error) { }); - 实例化对象:
let param = new URLSearchParams();
添加发送数据参数
param.append(“key”, “value”); - axios(utl:‘请求地址’,method:‘请求方式’,data/params:{k:v}).then((ok)=>{})
- 使用get发送数据的时候 使用params:{key:val}发送数据
- 使用post发送数据需要使用 var param=new URLSearchParams();修改传参方法
- 使用param.append(“uname”,“xixi”)添加数据并且使用data发送数据
请求封装
axiosLink(url){
// 用来封装一下axios
return new Promise((resolve,reject)=>{
axios({
url,
method:"get"
}).then((ok)=>{
resolve(ok)
}).catch((err)=>{
reject(err)
})
})
}
为什么要学习计算属性
- 视图的作用是什么?数据进行展示
下面的这段代码是干什么的 他是处理数据
既然视图中是数据展示 那么我们要进行数据处理的话 写在那里 计算属性 computed - 计算属性 就是vue实例中一个有计算data数据功能的属性
什么时候用应用场景 一条数据在不同位置想展示出不同的结果
<div id="demodiv">
<!-- 计算属性 就是vue实例中一个有计算data数据功能的属性
什么时候用应用场景 一条数据在不同位置想展示出不同的结果 -->
<h1> {{text.toUpperCase()}}</h1>
<h1>{{xiaoming}}</h1>
<h1>{{xiaohong}}</h1>
<h1></h1>
</div>
new Vue({
el:"#demodiv",
data:{
text:"abcdefgh"
},
methods:{
},
watch:{
},
// computed: {
// 需要返回的数据(处理完成之后的变量): function () {
// 必须 return 处理操作
// }
// }
computed:{
xiaoming(){
return this.text.toUpperCase().substr(1,3)
},
xiaohong(){
return this.text.toUpperCase()
}
}
})
计算属性与方法的区别
-
同样都是对数据进行处理并且在视图页面中能展示处理之后的数据
计算属性与方法有什么区别?
当数据没有改变的时候无论计算属性被调用了多少次 他都会只执行一次原因是因为 他在第一次
执行的时候会把结果保存在缓存中 但数据没有改变的时候 每次都是从缓存中读取方法 只要被调用就会执行 计算属性与方法有什么区别用一句话来解释 就是计算属性有缓存 方法没有 所以计算属性 对性能的消耗更低
<div id="demodiv">
{{fun()}}
{{fun()}}
<hr>
{{xiaoming}}
{{xiaoming}}
</div>
new Vue({
el:"#demodiv",
data:{
text:"abcdefg"
},
methods:{
fun(){
console.log("我是方法")
return this.text.toUpperCase()
}
},
computed:{
xiaoming(){
console.log("我是计算属性")
return this.text.toUpperCase()
}
}
})
声明周期与钩子函数
<div id="demodiv">
生命周期 vue从创建到销毁的过程
生命周期的钩子函数 :vue从创建到销毁的过程中被自动调用的函数
<h1>{{text}}</h1>
<button @click="fun()">点我修改数据</button>
<button @click="funb()">点我销毁实例</button>
</div>
<script>
new Vue({
el:"#demodiv",
data:{
text:"你好么么哒"
},
methods:{
fun(){
this.text="我被修改了"
},
funb(){
this.$destroy();
}
},
// beforeCreate(创建实例)、
// created(创建完成)、
// beforeMount(开始创建模板)、
// mounted(创建完成)、
// beforeUpdate(开始更新)、
// updated(更新完成)、
// beforeDestroy(开始销毁)、
// destroyed(销毁完成)
beforeCreate(){
console.log("创建实例")
},
created(){
console.log("创建完成")
},
beforeMount(){
console.log("开始创建模板")
},
mounted(){
console.log("创建完成")
},
beforeUpdate(){
console.log("开始更新")
},
updated(){
console.log("创建更新完成实例")
},
beforeDestroy(){
console.log("开始销毁")
},
destroyed(){
console.log("销毁完成")
}
})
</script>
全局和局的改变
<div id="demodiv">{{text|xiaohong}}</div>
<div id="demodivb">{{text|xiaohong}}</div>
<script>
// 局部过滤器只能在当前的实例的el范围内进行使用
// 全局过滤器 能在所有的实例中的el范围内都使用
// vue实例之前
// Vue.filter("过滤器的名字",(val)=>{
// return 您的逻辑
// })
Vue.filter("xiaohong",(val)=>{
return val.substr(1,2)
})
new Vue({
el:"#demodiv",
data:{
text:"你好abcdefg"
},
methods:{
},
filters:{
xiaoming(val){
return val.substr(1,4)
}
}
})
new Vue({
el:"#demodivb",
data:{
text:"你坏abcdefg"
},
methods:{
}
})
</script>
组件
- 组件的名字使用驼峰应该在调用的时候大写转小写并在大写之前加-
- 如果存在多个html应该有一个父元素来进行包裹
template:"…多个html" - 以上写法在template模板写内容比较麻烦,一般使用外部模板
template:外部模板id(把外部模板创建在实例与视图之外) - 组件如果想使用变量或方法 必须在组件自己内部创建,因为组件的作用域是独立的
全局组件 的创建 与 问题
-
组件使用驼峰式命名: 如果使用驼峰命名,在调用时需要将大写的字母写成小写,并在前面用 - 连接
-
如果组件有多个html标签: 必须有一个父元素将这些html包裹起来
-
这种写法,在写template模板里的内容时,非常麻烦。所有需要template外置模板
Vue.component('组件名字',{
template:'外部模板的Id' 外部模板创建在实例与试图之外
})
<template id="a">
<div>
<h1>223</h1>
<h1>223</h1>
</div>
</template>
组件的变量 与 方法
- 组件中如果想使用有变量或方法,必须在组件内部创建,因为组件的作用域是独立的
Vue.component('组件名字',{
template:'外部模板的Id', 外部模板创建在实例与试图之外
data(){ 数据的定义
return {
组件的名字:'组件的变量'
}
},
methods:{ 函数的创建
fun(){
函数
}
}
})
- data之所以是函数形式,是因为组件的每次调用硬是相互独立,不受影响的数据,函数的作用域就是独立的互不影响的
局部组件:与 el 同级
components:{
'组件名':{
template:'外部模板的Id / 内部模板',
data(){
return{
}
},
methods:{
xx(){
}
}
}
}
父子组件
components:{
'fu':{
template:'外部父模板的Id',
components:{ 子组件什么的位置实在父组件之内,并且子组件要在父组件模板中调用
'zi':{
template:'外部子模板的Id',
}
}
}
}
父子组件作用域
- 无论组件怎么嵌套,组件都是独立的,不能直接使用
组件传值 props 与methods、data()同级
* 正向传值 props 完成正向传值 父组件 给 子组件 数据
默认情况下props 没有值,必须在调用时传值
作用:接收组件外部传递进来的数据
props:['参数']
<template id="abc">
<div>
<zi title='123'></zi>
</div>
</template>
<template id="def">
<div>
{{title}}
</div>
</template>
与data一样,可以在函数中使用
props 选项的作用
- 作用:props选项用来声明它期待获得的数据
- props 本质:props 为元素属性
props 的声明
- 语法:如果是驼峰命名法需要把大写转小写前面加-
JS 中:
props:[‘message1’, ‘messAge2’…]
HTML 中:
<组件 message=‘val’mess-age2='val'></组件>
props 的使用
- 与 data 一样,props 可以用在模板中
- 可以在 vm 实例中像 this.message 这样使用
props验证
- 仅仅只会在控制台抛出警告,但不会对程序造成影响,props验证是给程序员看的
- 我们可以为组件的 prop 指定验证要求,例如知道的这些数据的类型。
- 为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。
home组件{
required:true //非空验证
type:String
}
// props:["hometext"]
// props验证(可以验证数据类型,不会对页面展示造成影响,props验证给程序员开发时看)
props:{
hometext:Number
hometext:[String,Number]//可以组合写
// default:100 带有默认值的数字
}
props 验证常见问题
- 为什么写的没有错但是没有错误提示?
- 生产版本也就是压缩版的文件删除了警告,所以使用非压缩版的js文件就可以看到错误
逆向传值
- 逆向传值
- 逆向传值必须要使用事件来触发
- 使用$emit来创建一个自定义事件,并且在其中正向传值
- 在子组件被调用时,使用@自定义事件名="父组件的函数 但不能加()要不然()加上会使用方法+
- 定义一个函数来接收子组件传递过来的数据,形参就是子组件的数据自动传入
slot(槽口) 的作用
- slot 作用:用来混合父组件的内容与子组件自己的模板
slot 的使用
- 语法:
- 声明组件模板:定义组件的时候留下slot等待调用的时候插入内容
<div>
<h2>
我是子组件的标题
</h2>
<slot>
只有在没有要分发内容时才会显示
</slot>
</div>
具名 slot
-
元素可以用一个特殊属性 name 来配置如何分发内容
-
多个slot可以有不同的名字
-
具名slot将匹配内容片段中有对应slot特性的元素
-
数量相同 内容不同的时候组件使用props
-
数量不同 内容不同的时候组件使用slot
vue脚手架
项目环境配置
- 安装 node.js
- 下载地址:https://nodejs.org/en/download/
扩展–修改npm 镜像源
- npm config set registry https://registry.npm.taobao.org
- 测试
- npm config get registry
- 安装淘宝镜像 cnpm (非必装,网络慢的情况可安装)
- npm install -g cnpm --registry=https://registry.npm.taobao.org
项目环境配置(2.0)
- 安装 vue-cli:npm install vue-cli -g(可以自动的构建项目结构和项目目录)
- 安装webpack:npm install webpack -g
- cd 到指定的项目路径中 并且初始化文件夹 npm init
- 创建项目:vue init webpack 项目名
项目环境配置(2.0)
- 切换到所创建的项目目录下:cd 你创建的项目文件夹
- 启动项目:npm run dev
项目目录结构分析(2.0)
文件解释
- build 中配置了webpack的基本配置,开发环境配置、生产环境配置(不建议修改)
- config中配置了路径端口值等
- node_modules为依赖的模块
- src放置组件和入口文件
- static放置静态资源文件
- index.html文件入口
卸载脚手架
- 如果已经全局安装了旧版本的vue-cli(1.x 或 2.x),需要卸载:npm uninstall vue-cli -g
vue-cli @4安装
- npm install -g @vue/cli
- vue --version查看版本
- vue create 项目名
- Manually select features 手工选择特性
- 选择vue版本–选择css预编辑语言–Linter/Formatter代码规范校验(不建议勾选)–选择2.0x版本
- cd 项目名
- npm run serve
vue-cli 配置端口 自动开启 热更新
- 在项目的根路径下根路径下根路径下根路径下创建一个文件名为vue.config.js
module.exports = {
devServer: {
open: true, //自动开启
port: 85, //修改端口
host: "127.0.0.1",
hotOnly: true, //热更新
}
}
扩展
v-for key的使用
- 为遍历数组或元素中的唯一标识,增加或删减元素时,通过这个唯一标识key判断是否是之前的元素,
- vue会直接对已有的标签进行复用,不会整个的将所有的标签全部重新删除和创建,只会重新渲染数据,
- 然后再创建新的元素直到数据渲染完为止
axios 请求环境配置
npm install --save axios下载axios
- 在src文件加下的main.js中添加
import axios from 'axios'
Vue.prototype.axios = axios
Axios–综合交互post get请求
- this.axios(utl:‘请求地址’,method:‘请求方式’,data/params:{k:v}).then((ok)=>{})
- 使用get发送数据的时候 使用params:{key:val}发送数据
- 使用post发送数据需要使用 var param=new URLSearchParams();修改传参方法
- 使用param.append(“uname”,“xixi”)添加数据并且使用data发送数据
mock模拟数据
- 在前端开发过程中,有后台配合是很必要的。但是如果自己测试开发,或者后台很忙,没时间,那么我们需要自己提供或修改接口。
- npm install --save mockjs
- 在src文件夹下创建相关文件与文件夹
mock
json
data.json
index.js
设置请求文件index.js
let Mock = require("mockjs")
Mock.mock("/user/details", "get", require("./json/data.json"))
引用mock在main.js
require("./mock") //新语法可以省去index.js,会默认去找当前路径下的index.js
请求
//1.下载axios
//2.引用axios
import axios from "axios"
// import { resolve, reject } from "core-js/fn/promise"
// export default(在一个文件中只能出现一次 但是我要暴露多个 那么就用下面的暴露方式)
// export 也是暴露可以出现多个(使用时必须解构)
export function one() {
return new Promise((resolve, reject) => {
axios({
url: "/user/details",
method: "get"
}).then((ok) => {
resolve(ok)
}).catch((err) => {
reject(err)
})
})
}
axios拦截器
请求拦截器
- 请求拦截器的作用是在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易。
响应拦截器
- 响应拦截器的作用是在接收到响应后进行一些操作,例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页。
axios拦截器初体验
在util工具文件夹中创建request.js文件用来编写拦截器。
import axios from "axios"
// 创建axios 赋值给常量service
const service = axios.create();
// 添加请求拦截器(Interceptors)
service.interceptors.request.use(function (config) {
// 发送请求之前做写什么
return config;
}, function (error) {
// 请求错误的时候做些什么
return Promise.reject(error);
});
// 添加响应拦截器
service.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default service
axios跨域
- proxyTable解决跨域
- 在vue.config.js中
发送请求中加入,其中链接地址api对应config.js中的api
export function poraxios() {
return new Promise((resolve, reject) => {
server.request({
url: "/api/data/cityinfo/101320101.html",
method: "get"
}).then((ok) => {
resolve(ok)
}).catch((err) => {
reject(err)
})
})
}
vue.config.js中加入
proxy: {
'/api': {
target: 'http://localhost:3000/', //对应自己的接口
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': ''
}} }
路由
传统页面跳转
- 使用a标签配合href属性,进行不同页面跳转
路由的作用
- 路由:所有的路径都经由这个模块进行重新分配(改变URL,在不重新请求页面的情况下,更新页面视图。)
- 根据 url 锚点路径,在容器中加载不同的模块
- 完成 SPA(单页面应用)的开发
路由原理
- 利用锚点完成切换
- 页面不会刷新
路由功能引入
- 官网推荐使用 vue-router.js 库来引入路由功能模块
定义路由
- 使用component来进行路由映射组件。name属性是命名路由通过一个名称来标识一个路由
const routes = [{
path: '/home',
name: 'Home',
component: Home
}
]
- 创建 router 实例,然后传
routes
配置
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
创建和挂载根实例。
- 通过 router 配置参数注入路由,从而让整个应用都有路由功能
new Vue({
router,
render: h => h(App)
}).$mount('#app')
- 使用标签设置路由跳转to属性用来设置跳转链接
- 路由出口表明路由模版显示的位置
js跳转
- 使用this.$touter全局路由的push()方法进行路由跳转
fun(){
this.$router.push("/index");
}
解决js跳转BUG
修改VueRouter原型对象上的push方法
Vue.use(VueRouter)
//获取原型对象上的push函数
const originalPush = VueRouter.prototype.push
//修改原型对象中的push方法
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
路由–扩展小知识
- 当 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active。
- 通过自动设置的类名方便进行路由导航样式设置
捕获所有路由–扩展小知识
- 常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*)
{name:"tema",path:"*",component:tema}
- 匹配任意开头的路径使用通配符(*)
{name:"tema",path:"/demo-*",component:tema}
- 当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: ‘*’ } 通常用于客户端 404 错误。
路由匹配优先级–扩展小知识
- 同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
编程式的导航–扩展小知识
- 之前已经使用js方式进行跳转路由–这种方式被称之为编程式导航
- 使用router-link进行跳转路由–称之为声明式
- 扩展路由跳转方式:
- router.replace()替换
- 与push()唯一的不同就是,它不会向 历史url记录中添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
- this.$router.go(n)这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
动态路由
动态路由匹配
- 动态路由也可以叫做路由传参
- 组件的显示内容经常会根据用户选择的内容不同来在同一个组件中渲染不同内容。那么在这个时候就需要动态路由
动态路径参数
- 使用动态路由匹配中的 动态路径参数来进行路由配置。
- 注意:动态路径参数 以冒号:开头
const routes = [{
path: '/home/:id/:name',
name: 'Home',
component: Home,
}]
绑定参数
- 路由导航绑定参数的两种方式 但是注意 params只能通过路由配置中的name属性来引用路由
<router-link to="/index/参数1/参数2"></router-link>
//第二种方式:不能忘 同时parmas只能通过name来引入路由
<router-link :to="{name:'Home',params:{id:'参数1',name:'参数2'}}"></router-link>
- js方式进行参数绑定
fun(){
this.$router.push("/index/js参数1/js参数2");
//注意params只能通过name来引入路由
this.$router.push({name:'Home',params:{id:'js参数1',name:'js参数2'}})
}
获取路由传入参数
- 如果想得到路径参数那么使用$route.params.id
fun(){
return this.$route.params.a
}
- 使用this实例中的this.$route.params.id进行调用
created(){console.log("我是"+this.$route.params.a);}
嵌套路由
- 二级路由中路径不加/
- 在父组件中设置路由出口 router-view
- 由于没有加/ 所以在路由导航的时候 应该是 /一级/二级路由
- 如果加了/ 那么路由导航的时候 应该是 /二级
const routes = [{
path: '/home',
name: 'Home',
component: Home,
// 二级路由中,路径不要加/(不加反斜杠)
children:[{
path: 'home',
name: 'Home',
component: Home,
}]
}]
路由重定向
- 重定向也是通过 routes 中的redirect属性配置来完成
{path:"/",redirect:"/home"}
路由守卫/导航守卫
导航守卫
- vue项目中经常在路由跳转前做一些验证,比如登录验证,是网站中的普遍需求。
导航守卫-全局前置守卫
- 当一个导航触发时,全局前置守卫(在进入组件之前)按照创建顺序调用。
- vue-router 提供的 router.beforeEach((to,from,next)=>{})可以方便地实现全局前置导航守卫
- to:即将要进入的目标 路由对象
- from: 当前导航正要离开的路由
- next: 下一步执行
导航守卫-全局后置钩子
- 当一个导航触发时,全局后置钩子(在进入组件之后)调用。
- vue-router 提供的 router.afterEach((to, from) => {})实现全局后置守卫
- to:即将要进入的目标 路由对象
- from: 当前导航正要离开的路由
导航守卫-路由独享的守卫
- 与全局前置守卫相比路由独享守卫只是对当前路由进行单一控制参数和全局前置守卫相同
- 在路由配置上直接定义 beforeEnter 进行路由独享守卫定义
{name:"Home",path:"/home",component:Home,beforeEnter:(to,from,next)=>{
alert("提示信息!")
next("/login") //下一步要干的事 如果不写任何值则会进入当前路由
}}
导航守卫-组件内的守卫
- 组件内守卫只会对当前组件生效。
- beforeRouteEnter在进入组件前调用
- beforeRouteLeave离开路由之前
导航守卫-组件内的守卫beforeRouteEnter
- 在组件中使用beforeRouteEnter(to, from, next) {}来进行进入组建前的钩子
//与data同级
beforeRouteEnter(to, from, next) {
//因为通常在组件内守卫需要调用data数据完成指定逻辑 所以必须在next()中使用回调函数进行调用
如果不在的话 是无法调用的 欣慰在调用beforeRouteEnter的时候因为守卫执行前,组件实例还没有被创建
next((a)=>{
alert("提示信息!")
})
}
导航守卫-组件内的守卫beforeRouteLeave
- 在组件中使用beforeRouteLeave(to, from, next) {}来进行离开组件的钩子
beforeRouteLeave(to, from, next) {
if(confirm("确定离开?")==true){
next();
}else{
//不进行下一步(也就是不从当前离开)
next(false)
}
}
路由知识点扩展—数据获取
- 进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
- 从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。
params与query区别
- 用法上的:
- query要用path来引入,params要用name来引入,接收参数都是类似的,分别是this. r o u t e . q u e r y . n a m e 和 t h i s . route.query.name和this. route.query.name和this.route.params.name。
- url展示上的
- params类似于post,query更加类似于我们ajax中get传参,说的再简单一点,前者在浏览器地址栏中不显示参数,后者显示,所以params传值相对安全一些。
$router 和 $route的区别
- $router是VueRouter的一个对象,router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。
- $route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等
hash模式
- hash模式url里面永远带着#号,我们在开发当中默认使用这个模式。
history模式
- history模式没有#号,是个正常的url适合推广宣传。
- 考虑url的规范那么就需要使用history模式,因为当然其功能也有区别,在开发app的时候有分享页面,这个分享出去的页面就是用vue做的,把这个页面分享到第三方的app里,有的app里面url是不允许带有#号的,所以要将#号去除那么就要使用history模式,history模式还有一个问题就是,做刷新操作,会出现404错误,那么就需要和后端人配合让他配置一下apache或是nginx的url重定向,重定向到你的首页路由上。
const router =new VueRouter({
mode:'history',//设置history模式
routes
})
配置解析别名—修改文件夹引用别名
- 第一个参数:是你设置的别名 第二个参数:所指向的路径
configureWebpack:{
resolve:{
alias:{
// "别名":"对应的文件夹"
"com":"@/components"
}
}
}
动态组件
- 让多个组件使用同一个挂载点,并动态切换,这就是动态组件。
- 使用 is 特性来切换不同的组件
<component :is="com"><component>
refs
- 在Vue中一般很少会用到直接操作DOM,但不可避免有时候需要用到,这时我们可以通过ref和$refs这两个来实现
- ref 被用来给元素或子组件注册引用信息, 引用信息将会注册在父组件的 $refs 对象上,如果是在普通的DOM元素上使用,引用指向的就是 DOM 元素,如果是在子组件上,引用就指向组件的实例。
- $refs 是一个对象,持有已注册过 ref 的所有的子组件。
用在dom元素上
- 可以获取DOM对象
- 绑定:
我是ref
- 获取: this.$refs.demo
用在组件上
- ref绑定到组件上可以快速访问到组件实例,及其相关属性方法
VUEX
兄弟组件传值----传统
同级/兄弟组件传值
- 在需要传值的兄弟组件之上创建一个父组件 A逆向给父组件 父组件在正向给B组件
兄弟组件传值----中央事件总线
- 在src/下创建一个文件夹用来存放xxx.js文件在其中只创建一个新的Vue实例,以后它就承担起了组件之间通信的桥梁了,也就是中央事件总线。
import Vue from "vue"
export default new Vue
- 创建一个A组件,引入事件总线的js文件,接着添加一个按钮并绑定一个点击事件,进行自定义事件抛出
<template>
<div>
eventa
<button @click="fn()">点我传递数据去eventb</button>
</div>
</template>
<script>
import EventBus from "@/eventBus/index.js"
export default {
data(){
return{
text:"我是eventa的数据"
}
},
methods:{
fn(){
// 逆向传值的方法
// this.$emit("apao",this.text)
EventBus.$emit("apao",this.text)
}
}
}
</script>
- 再创建一个B组件,引入eventBus事件总线,在mounted钩子,监听了自定义事件,并把传递过来的字符串参数传递给了on监听器的回调函数
- $on:监听当前实例上的自定义事件
<template>
<div>
eventb
</div>
</template>
<script>
import EventBus from "@/eventBus/index.js"
export default {
mounted(){
// 监听实例上的自定义事件
EventBus.$on("apao",(val)=>{
console.log(val);
})
}
}
</script>
总结
- 创建一个事件总线,例如demo中的eventBus,用它作为通信桥梁
- 在需要传值的组件中用bus.emit触发一个自定义事件,并传递参数(emit前加美元符)
- 在需要接收数据的组件中用bus.$on监听自定义事件,并在回调函数中处理传递过来的参数
单一状态树
- Vuex 使用单一状态——用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 ”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。
import Vue from 'vue'
import Vuex from 'vuex'
//使用中间件使用vuex
Vue.use(Vuex)
需要让外部使用实例化的store对象 export就是导出的意思
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
vuex–State数据源
- vuex中的数据源state,我们需要保存的数据就保存在这里
state: {
obj:
{ id: 1, title: "我是谁", content: "我就说我 没办法就这么拽" }
}
vuex–使用数据源
- 要使用首先在main.js引入vuex。
- 一对大括号的原因是,指定要从其他模块导入的变量名。
import {store} from './store/store'
- 可以使用$store.state.xx调用
{{$store.state.obj.id}}
或使用
mounted(){
console.log(this.$store.state.obj.id)
}
- 使用数据的组件中使用计算属性调用
- this.$store.state.xxx
computed:{
obj(){
return this.$store.state.obj
}
}
vuex–state扩展知识点
-
Vuex就是一个仓库,仓库里面放了很多对象。其中state就是数据源存放地,对应于与一般Vue对象里面的data
-
state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据发生改变,依赖这个数据的组件也会发生更新
-
它通过mapState把全局的 state 和 getters 映射到当前组件的 computed 计算属性中
vuex–Mutations
- mutations,里面装着一些改变数据方法的集合,就是把处理数据逻辑方法全部放在mutations里面(当触发事件的时候想改变state数据的时候使用mutations)
mutations: {
// 都是对vuex数据的修改操作
up(state) {
state.obj.title = "我管你是谁"
}
}
- 注意:不能直接调用一个 mutations 中的处理函数 要使用this.$store.commit() 来进行调用
methods:{
fu(){
// 调用vuex的修改操作 commit 就是用来调用mutations里面的修改动作
this.$store.commit("up")
}
}
vuex–Mutations 提交载荷(Payload)
- 之前的只是一个简单的修改state中的属性
- 在实际项目中往往会有值传递给Mutations 给store.commit传一个附加参数,他就叫做mutation的载荷
//组件内
data(){
return{
text:""
}
},
methods:{
fun(){
this.$store.commit("upb",this.text)
}
}
//store内
upb(state, payload) {
// 传参修改
state.obj.content = payload
}
- 多个参数也可以传递一个对象
- this.$store.commit(‘add’, { ‘num’: 20 } )
vuex–Mutations 问题
- vuex中页面刷新数据丢失问题
- 使用vue进行开发的过程中,状态管理工具一般使用vuex进行管理。但是修改后的vuex数据存储在内存中,所以当前页面刷新数据会产生丢失现象
- 使用H5特性本地存储
created () {
//在页面加载时读取localStorage里的状态信息
if (localStorage.getItem("data") ) {
//replaceState替换数据 Object.assign合并对象
this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(localStorage.getItem("data"))))
}
//在页面刷新时将vuex里的信息保存到localStorage里
window.addEventListener("beforeunload",()=>{
localStorage.setItem("data",JSON.stringify(this.$store.state))
})
},
vuex–Actions
- Actions 进行操作,使用Actions 进行异步操作(异步请求)
actions: {
// 用来处理异步操作
democact(context) {
democapi.vuexlink().then((ok) => {
console.log(ok);
}).catch((err) => {
console.log(err);
})
}
},
- 分发 Action:Action 通过 this.$store.dispatch(“xxxx”);方法触发
methods:{
fun(){
// 调用vuex异步操作
this.$store.dispatch("democact")
}
},
- Action同样支持载荷(可以传参 这里加i是传参(i))
<button @click="fun(i)">点我调用异步请求数据</button>
methods:{
fun(i){
// 调用vuex异步操作
this.$store.dispatch("democact",i)
}
},
actions: {
// 用来处理异步操作
democact(context) {
democapi.vuexlink().then((ok) => {
console.log(ok.data.arr.con);
// 修改state必须通过commit来调用mutations来修改state
context.commit("democup", ok.data.arr.con)
}).catch((err) => {
console.log(err);
})
}
},
vuex–Actions总结
- Actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据(但是还是通过mutation来操作,因为只有它能操作)
vuex–modules
- 在Vue中State使用是单一状态树结构,应该的所有的状态都放在state里面,如果项目比较复杂,那state是一个很大的对象,store对象也将对变得非常大,难于管理。
- module:可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
- 创建文件容纳模块
文件夹==>文件名.js - 在vuex中引用
import { hwstore } from "./modules/hwstore.js"
modules: {
hwstore
}
- 使用数据:$store.state.模块名.xxx
$store.state.hwstore.arr.id
- 修改数据:和没有modules的时候一样。
fu(){
// 调用vuex的修改操作 commit 就是用来调用mutations里面的修改动作
this.$store.commit("up")
},
vuex–Getters
vuex–Getters属性
- getters相当于之前组件中学习的计算属性,getters属性主要是对于state中数据的一种过滤
- 使用场景:在项目开发中,有时候希望对state中的某个属性在多个组件中展示出不同状态
getters:{
//接收state作为参数
newarr(state){
var newarr=state.arr.filter((v,i)=>{
//可以用来过滤
if(v.id>3){
return v
}
})
//不能忘记
return newarr;
}
}
export let about = {
state: {
text: "abcdefghigk"
},
getters: {
// vuex计算属性 一条数据在不同位置可以展现不同形态
gettersa(state) {
return state.text.toUpperCase()
},
gettersb(state) {
return state.text.substr(1, 4)
},
gettersc(state) {
return state.text.toUpperCase().substr(1, 4)
}
},
mutations: {},
actions: {},
modules: {}
}
vuex–使用Getters数据
- 与使用state相同在组件中的计算属性当中使用 this.$store.getters.xxx来进行调用
newarr(){
return this.$store.getters.newarr
}
- 数据调用
{{this.$store.state.about.text}}
vuex–表单处理
- 当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 v-model 会比较棘手。在用户输入时,v-model 会试图直接修改 state数据。在严格模式中,由于这个修改不是在 mutation 函数中执行的, 这里会抛出一个错误。
vuex–表单处理解决方式
- Vuex 的思维解决,给 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用 action。
<input type="text" name="uname" :value="newtexteobj[0].age" @input="fn()"/>
- 调用函数
fn(e){
this.$store.dispatch("方法名",e.target.value)
}
vuex–严格模式
- 严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
- 开启严格模式,仅需在创建 store 的时候传入 strict: true
不要在发布环境下启用严格模式!
keep-alive
- 在上一个demo中我们不停的切换两个标签页的内容时候,会发现在练习我们中选择好的内容,切换路由之后会恢复初始化。 也就是说之前的状态丢失。
- 原因是每次切换路由的时候,Vue 都创建了一个新的 组件实例
- 解决这个问题,我们可以用一个 元素将其路由出口包裹起来。
- 在切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性
<keep-alive>
<component :is="com"></component>
</keep-alive>
keep-alive 属性
- 在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。
<keep-alive include="Demob">
<component :is="com"></component>
</keep-alive>
keep-alive 的钩子函数
- 这两个生命周期函数一定是要在使用了keep-alive组件之上。
- activated 类型:func 触发时机:keep-alive组件激活时使用;
- deactivated 类型:func 触发时机:keep-alive组件停用时调用;
activated(){
console.log("使用的时候");
},
deactivated(){
console.log("停止的时候");
},
自定义指令
除了内置指令外, 用户自定义的指令
- 局部指令定义:
directives:{
自定义指令的名字:{
自定义指令钩子函数(el){
操作逻辑
}
}
},
- bind:绑定指令到元素上,只执行一次
- inserted:绑定了指令的元素插入到页面中展示时调用,基本上都是操作这个钩子函数
- update:所有组件节点更新时调用
- componentUpdated:指令所在组件的节点及其子节点全部更新完成后调用
- unbind:解除指令和元素的绑定,只执行一次
打包上线
打包上线
- npm run build
- 在项目下面会生成一个dist文件夹就是打包好的内容 运行其中的html文件即可
运行之后会发现白屏 在控制台会发现有报错
解决:修改资源路径为./
- 将下边代码粘贴到 vue.config.js 中,修改对应内容即可
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
// 基本路径
publicPath: './', //部署应用包时的基本 URL
outputDir: 'dist', // 输出文件目录
assetsDir: '',//放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录
runtimeCompiler: false, //是否使用包含运行时编译器的 Vue 构建版本。设置为true可以使用template
productionSourceMap: process.env.NODE_ENV === 'development',//生产环境是否生成 是用来报错时定位到代码位置 文件
lintOnSave: true,
chainWebpack: config => {
},
configureWebpack: (config) => {
// ============去除console============
if(process.env.NODE_ENV === 'production'){
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
}
// ============去除console end============
},
// css相关配置
css: {
// 是否使用css分离插件 ExtractTextPlugin
extract: true,
// 开启 CSS source maps?
sourceMap: false,
// css预设器配置项
loaderOptions: {},
// 启用 CSS modules for all css / pre-processor files.
modules: false
},
parallel: require('os').cpus().length > 1,//是否为 Babel 或 TypeScript 使用 thread-loader
// PWA 插件相关配置
// see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa
pwa: {},
// webpack-dev-server 相关配置
devServer: {
open: process.platform === 'darwin',
host: '0.0.0.0',
port: 8888,
https: false,
hotOnly: false,
// 设置代理
proxy: {
'/api': {
target: 'http://localhost:3000/', //对应自己的接口
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': ''
}
}
},
},
// 第三方插件配置
pluginOptions: {
// ...
}
}
设置路由模式
- router-view中的内容显示不出来
- 需要关闭路由的history模式,因为当前模式需要后台配合。
- nginx服务器配置try_files重定向
location / {
try_files $uri $uri/ /index.html;
}
打包优化
- productionSourceMap是用来报错时定位到代码位置(就是.map文件 作用:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。 )配置为false取消配置。可以减少构建时间,优化构建速度
- productionSourceMap: process.env.NODE_ENV === ‘development’
- process.env.NODE_ENV是判断生产环境或开发环境。
- 图片压缩
- npm install --save babel-polyfill
- 配置vue.config.js
chainWebpack: config => {
.rule("images")
.use('image-webpack-loader')
.loader('images-webpack-loader')
.options({bypassOnDebug:true})
.end()
}
自动忽略console.log语句
- npm install terser-webpack-plugin --save
- 配置vue.config.js
const TerserPlugin = require('terser-webpack-plugin')
configureWebpack: (config) => {
// ============去除console============
if(process.env.NODE_ENV === 'production'){
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
}
// ============去除console end============
}
Nodejs
魔术变量
- _filename:表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径
- _dirname:表示当前执行脚本所在的目录
try···catch
try {
// 可能会出错的代码
} catch (error) {
// 出错了怎么办
}
Nodejs
readdir() 异步读取
- 异步则不会阻塞代码的执行
- 异步调用会将读取任务下达到任务队列,直到任务执行完成才会回调
- 语法:fs.readdir(path,options,callback(err,files))
- path(必选),表示要读取的路径。
- options(可选)参数。
- callback 是回调函数,第一个err错误信息,data成功内容
- 注意在node的异步操作当中 有错误的回调优先原则,在回调的第一个行参表示错误对象默认为null
mkdir() 异步创建目录 mkdirSync()同步创建目录
- 语法:fs.mkdir(path,[mode],[callback(err)])注意不能创建一个已存在的文件夹
- path(必选),表示创建的目录。
- mode(可选),表示创建模式。
- callback 是回调函数,。err错误信息
var fs = require("fs")
fs.mkdir("./aa", (err) => {
// fs模块的异步是错误优先原则
if (err) {
console.log("错");
return
}
console.log("ok");
})
fs方法
- fs.unLink()删除 Node删除目录 rmdir() 注意只能删除空文件夹
- fs.rename(“路径”,“修改参数”,回调函数)修改
//Promise封装
//引入fs模块
var fs = require("fs")
let mk = () => {
return new Promise((resolve, rejects) => {
fs.mkdir("./aa", (err) => {
// fs模块的异步是错误优先原则
if (err) {
console.log("错");
return
}
console.log("ok");
})
})
}
//调用
mk().then((ok) => {
console.log(ok);
}).catch((err) => {
console.log(err);
})
nodeJS特性–扩展知识
回调地狱
- 回调函数:是必须得依赖另一个函数执行调用,它是异步执行的,也就是需要时间等待,典型的例子就是Ajax应用
- 回调地狱:函数作为参数层层嵌套
- 在node中 因为非阻塞i/0的特性,node中经常会产生回调地狱
- 但是回调地狱产生之后会导致代码可读性非常差
扩展知识–promise对象
- 解决回调地狱方式:promise asys/awiat 蓝鸟插件…
- promise对象简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)
- Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
HTTP模块
- 基本服务器创建过程HTTP模块
- http模块 是node重要的核心模块
- webServer 方便的实现webServer服务器的创建
- 方法 createServer(function(req,res){})进行服务器创建 listen()来监听端口
let http = require("http")
//创建服务器 webserver
http.createServer((req, res) => {
res.writeHead(200, { "Content-type": "text/html;charset=utf-8" })
res.end("我是一个服务")
}).listen(8899) //监听端口
原生路由
通过URL路径来区分不同的请求,从而找到不同的功能模块来进行执行。
- 路由就是根据对不同的请求,找到不同的代码完成处理
URL模块
- 对当前的url进行相关操作url.parse(req.url,true);//得到请求的url数据 加上第二个参数为true 就可以把当前url中的query页面数据转换成对象方便使用
let http = require("http")
let url = require("url")
//创建服务器 webserver
http.createServer((req, res) => {
res.writeHead(200, { "Content-type": "text/html;charset=utf-8" })
var domeurl = url.parse(req.url, true) //把请求来的url转换成对象
// 原生webserver会有二次请求,会造成程序数据获取错误
if (domeurl.pathname == "/favicon.ico") {
return
}
if (domeurl.pathname == "/a") {
console.log("你访问的是a路由");
}
console.log(domeurl);
res.end("我是一个服务")
}).listen(8899) //监听端口
express
什么是Express
- express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性和丰富的 HTTP 工具
- 功能:
- 扩展了web所需要的基本功能
- 丰富了HTTP工具
- 可以快速搭建一个网站
- 安装:npm install --save express
Express----创建api接口
var express=require("express");
var app=express();
app.get("/user/login",function(req,res){
res.send({msg:”ok”})
})
app.get("/ser/home",function(req,res){
res.send({msg:”ok”})
})
var server=app.listen(3000)
- app.post() 接受post请求;
- app.all() 方法让路由函数接收所有指定URL的HTTP方法。
- 路径中使用 * 通配符可以匹配所有例:app.get("/a*b",function(req,res){}
创建router文件夹并创建路由文件
// 存放路由的文件
var express=require("express");
var router=express.Router()
// 设置路由
router.get("/login",function(req,res){
res.send({msg:"我是login"})
})
router.get("/zhuce",function(req,res){
res.send({msg:"我是zhuce"})
})
// 暴露路由
module.exports=router
使用路由文件
var express=require("express");
var app=express();
// 引用路由文件
var userRouter=require("./userRouter");
// 中间件中使用路由
app.use("/user",userRouter)
// 请求是localhost:3000/user/路由文件中的地址
app.listen(3000)
静态资源文件–webserver
- 使用中间件配合
app.use(express.static(path.join(__dirname, “文件夹名”)));
//express路由使用
var express = require("express")
// 使用express路由
var router = express.Router()
router.get("/a", (req, res) => {
res.send("我是首页接口a")
})
router.get("/b", (req, res) => {
res.send("我是首页接口b")
})
router.get("/c", (req, res) => {
res.send("我是首页接口c")
})
module.exports = router
//服务端
var express = require("express")
var app = express()
// 我要使用那些路由 必须引用
let homer = require("./routers/homerouter.js")
let listr = require("./routers/listrouter.js")
// 使用中间件调用分发路由
app.use("/home", homer)
app.use("/list", listr)
app.listen(9988)
MongoDB
MongoDB操作数据库
- 查看当前数据库 (show dbs)
- 增加/切换数据库 (use [db名称])
- 删除数据库 (1.use [db名称] 2.db.dropDatabase()//注意大写)
MongoDB操作数据集合
- 查看数据库当中的集合 (show collections;)
- 创建collection
- (db.[collection名称].insert({json数据}); 创建添加数据)
- (db.createCollection([collection名称]); 只创建)
- 删除集合
- (db.[collection名称].drop()😉
MongoDB文档管理-查询
- 查询数据库语句
- (db.[collection名称].find();//查询集合中所有的记录)
- (db.[collection名称].find().pretty();//格式化查询使得查询出来的数据在命令行中更加美观的显示(有显示格式的优化),不至于太紧凑)
Mongoose 与 Node 操纵 Mongodb数据库
- mongoose是nodeJS提供连接 mongodb的一个库,
- 便捷操作MongoDB的对象模型工具(Mongoose的操作是以对象为单位的)
驱动下载
- npm install mongoose --save(vue项目中下载模块)
连接mongdb
- //链接数据库使用解析器来解析本次连接
- mongoose.connect(“mongodb://localhost:27017/连接的(库)”,{ useNewUrlParser: true,useUnifiedTopology:true })
- var db=mongoose.connection;//设置数据库链接对象
- // 监听成功失败
- db.on(“err”,console.error.bind(console,“aaa”));
- db.on(“open”,()=>{
console.log(“链接成功”) - })
// 1.引用mongoose
let mongoose = require("mongoose")
// 2.设置连接参数 conect
mongoose.connect("mongodb://localhost:27017/sinner", { useNewUrlParser: true, useUnifiedTopology: true })
// 3.开始连接
var db = mongoose.connection;
// 4.监听连接状态
db.on("err", console.error.bind(console, "数据库连接失败"))
db.on("open", () => {
console.log("数据库链接成功");
})
- 创建schema对象:mongoose操作数据库都是通过schema对象
//文字说明
定义文档构成(类似创建数据库表字段方便添加内容的时候使用)
var userSchema= new mongoose.Schema({
行名:数据类型,//内容与数据类型
行名:{type:Number,default:默认值},
行名:{type:String,required:true 不能为空}
})
//代码部分
// 设置数据库链接文档对象
var conlsc = new mongoose.Schema({
name: String
})
- Schema对象转换成数据模型
注意mongoose会默认给我们的集合名末尾加个s或者我们手动加个s
- var colUser=mongoose.model(“集合名”,创建的schema对象);
// 设置集合
let usss = mongoose.model("user", conlsc)
module.exports = usss//暴露
插入
- 设置插入内容
- var demoUser = new colUser(
key:“val”,
key:"“val”})
// 添加
// 1.设置插入数据
let usssadd = new usss({
name: "千手修罗"
})
- 插入使用save()进行插入
// 2.开始插入数据
usssadd.save().then((ok) => {
console.log(ok);
}).catch((err) => {
console.log(err);
})
查询
- 使用find()进行查询操作
- 查询全部:
// 查询
usss.find().then((ok) => {
console.log(ok);
// 把查询的数据返回给前台
res.send({ status: 200, msg: "我是查询的信息", data: ok })
}).catch((err) => {
console.log(err);
})
- 查询指定内容
// 查询
usss.find({字段:"字段值"}).then((ok) => {
console.log(ok);
// 把查询的数据返回给前台
res.send({ status: 200, msg: "我是查询的信息", data: ok })
}).catch((err) => {
console.log(err);
})
解决跨域
jsonp:
- 网页中添加一个script元素,向服务器请求json数据,这种做法不受同源策略的限制,服务器接收到请求后,把数据放在一个指定名字的回调函数里传递回来
CORS:全称"跨域资源共享"
- CORS需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORS,IE则不能低于IE10。
- CORS的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送ajax请求并无差异。
- 实现CORS的关键在于服务器,只要服务器实现CORS接口,就可以实现跨域通信。
Nodejs解决跨域(后端解决)
app.use("/", function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE,OPTIONS');
//注意必须写next()
next()
})
http-proxy-middleware
- 实现代理跨域:用于把请求代理转发到其他服务器的中间件。
得到前台数据
get方式获得
- req.query.xxx----通过相应的query属性得到数据
post方式获得
- 引入body-parser模块默认情况下Express并不知道该如何处理该请求体,因此我们需要增加bodyParser,用于分析。
var uE = bodyParser.urlencoded({ extended: false })
这个方法也返回一个中间件,这个中间件用来解析body中的urlencoded字符, 只支持utf-8的编码的字符
app.post('/', uE , function (req, res) {。。。。})
- req.body.xxx----通过相应的body属性得到数据
crypto 模块进行加密
- crypto模块:封装了一系列密码学相关的功能 npm i crypto --save下载
- var 变量(加密之后返回结果) =crypto.createHash(‘md5’).update(加密数据).digest(‘hex’);
(md5加密)
var md5password = crypto.createHash('md5').update(req.query.password).digest('hex');
console.log(md5password);
Mixins(混入)
- Mixins是一种分发Vue组件中可复用功能的非常灵活的一种方式。
- 页面的风格不用,但是执行的方法和需要的数据类似,我们是选择每个都写呢还是提取出公共部分?在这个时候可以使用混入来完成。
使用
- 在项目的components新建一个MiXins文件夹内部创建index.js。
const Mixin={
data(){
return{
text:"复用数据",
}
},
methods:{
fn(){
window.console.log(this.text)
}
}
}
export default Mixin;
- 在你需要引入的页面中注册这个mixins,并且使用。
import Mixin from '@/minxins/index.js'
export default{
mixins:[Mixin]
}
- 页面中可以正常的使用mixins中的数据
全局混入
- 在任何组件中都可以使用
- 在main中引用并使用
import Mixin from '@/minxins/index.js'
Vue.mixin(Mixin);
在组件中即可随意使用混入中的数据
$set
- 在日常开发中或者是面试中 会遇见一个问题:为什么修改了data中的数据,但是视图没有更新?
- 总结这个问题就是:当vue的data里边声明或者已经赋值过的对象或者数组(数组里边的值是对象)时,向对象中添加新的属性,如果更新此属性的值,是不会更新视图的。
- 根据官方文档:定义如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。
原因
- 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
- 受现代 JavaScript 的限制 (以及废弃 Object.observe),Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
export default {
data(){
return{
obj:{
text:"大傻子"
}
}
},
methods:{
fn(){
this.$set(this.obj,'age',18)
}
}
}
两天
图片上传
- multer模块 npm i multer --save下载
multer.diskStorage({//配置 设置保存文件的地方
destination 设置资源的保存路径。注意,如果没有这个配置项,默认会保存在/tmp/uploads下。此外,路径需要自己创建.
filename 设置资源保存在本地的文件名
})
- 后台代码
var storage = multer.diskStorage({
//设置上传后文件路径,imgup文件夹会自动创建(建议手动提前创建这个文件夹)。
destination: function(req, file, cb) {
cb(null, './imgup存放图片的文件夹')
},
//给上传文件重命名,获取添加后缀名
filename: function(req, file, cb) {
cb(null,"aa.jpg存放图片的文件名" );
}
});
var upload = multer({
storage: storage
});
router.post('/upload', upload.single('logo'), function(req, res, next) {
// logo是前台向后台发送数据的key值 在传递数据的时候必须一样
res.send({msg: '上传ok'});
});
- form表单方式发送数据
<!-- enctype="multipart/form-data" 告诉表单以文件流形式发送 -->
<form action="http://localhost:3000/file/upfile"method="post" enctype="multipart/form-data">
<h2>单图上传</h2>
<input type="file" name="logo">
<input type="submit" value="提交">
</form>
- axios方式发送数据
<input type="file" ref="file" id="file">
<button @click="upfun()">点我上传图片</button>
upfun(){
var el=this.$refs.file.files[0]//files得到当前图片
var formdata=new FormData()//创建出formdata对象(异步上传文件)
formdata.append("logo",el)//添加上传文件的key 和后台接受的key要相同 el就是当前得到的图片
// 调用封装的数据请求
uplink(formdata).then((ok)=>{
console.log(ok)
})
}
//axios上传图片配置
export function uplink(data){
console.log(data)
return new Promise((resolve,reject)=>{
service.request({
url:url.shop,
method:"post",
data,//上传图片的信息
cache: false, //不必须不从缓存中读取
processData: false,//必须处理数据 上传文件的时候,则不需要把其转换为字符串,因此要改成false
contentType: false,//必须 发送数据的格式
}).then((ok)=>{
resolve(ok)
}).catch((err)=>{
reject(err)
})
})
}
- 了解更多
app.post('/upload', upload.single('logo'), function(req, res, next) {
console.log(req.file)
console.log('文件类型:', req.file.mimetype);
console.log('原始文件名:', req.file.originalname);
console.log('文件大小:', req.file.size);
console.log('文件保存路径:', req.file.path);
});
限制上传大小
app.post('/upload',upload.single('logo'),function(req,res,nex){
//logo是前台向后台发送的数据key值 在传递数据的时候必须一样
//判断上传图片大小
if(req.file.size>50000){
res.send({msg:"图片太大"})
}else{
res.send({msg:"上传ok"})
}
})
- 提交多个之后大家发现一个问题 就是一直只有一张图片原因是因为 文件名相同覆盖掉了
- 防止上传图片名重复
//给上传文件重命名,获取添加后缀名
filename: function(req, file, cb) {
// 修改上传的文件名
//得到后缀名使用split把原始文件名用.分隔开变成一个数组然后去出第一个下标的内容就是后缀名
let filen = file.originalname.split(".")[1]
//生成一个不重复的文件名 生成当前的时间戳
let fanme = new Date().getTime()
cb(null, `${fanme}.${filen}`);
}
token 就是一个加密的字符串
- npm install --save jsonwebtoken下载
- 当用户登陆成功后 后端会生成一个token(也就是一段保存了用户登陆状态的加密字符串)并且会把这个token返回前端
- 前端 在Ajax的响应成功的地方能得到这个token 把这个token保存在本地存储或其他存储位置
- 在每次给后台发送数据的时候 携带这个这个token 把token发送给后端-----》把这个token解密得到(之前加密的登录状态)判断这个状态得知用户是否登录过
react
什么是React
- React起源于Facebook
- React 是一个用于构建用户界面的 javascript 库。
- React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。
- Facebook 需要解决的问题:构建数据不断变化的大型应用。
虚拟Dom—快减少更新次数 减少更新区域
- 虚拟dom相当于在js和真实dom中间加了一个缓存。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都首先重新构建整个DOM树(减少页面更新次数),然后React将当前整个DOM树和上一次的DOM树进行对比(DOM Diff算法-最小化页面重绘),得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。
为什么虚拟 dom 会提高性能?(重要)
- 虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
- 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
diff算法的作用
- 计算出虚拟DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面。
React的diff算法
-
什么是调和?
-
将Virtual(虚拟)DOM树转换成actual(真实)DOM树的最少操作的过程称为调和 。
-
什么是React diff算法?
-
diff算法是调和的具体实现。
React 特点
- 声明式设计 −React采用声明范式,可以轻松描述应用。(开发者只需要声明显示内容,react就会自动完成)
- 高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
- 灵活 −React可以与已知的库或框架很好地配合。
- 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。(把页面的功能拆分成小模块–每个小模块就是组件)
- 单向数据流-- React是单向数据流,数据主要从父节点传递到子节点(通过props)。如果顶层(父级)的某个props改变了,React会重渲染所有的子节点
React 组件特性
-
React的核心就是组件:组件的设计目的是提高代码复用率,降低测试难度和代码的复杂程度。
-
提高代码复用率:组件将数据和逻辑进行封装。
-
降低测试难度:组件高内聚低耦合(各个元素高集成度低关联性),很容易对单个组件进行测试。
-
代码的复杂程度:直观的语法,可以极大提高可读性。
组件化编程思想
- 灵活的组件化设计思想,可以让你的页面功能更具复用性。同时低耦合度的组件可以让功能之间的影响降到最低!
React 依赖包下载
- react核心包: npm install react --save
- react-dom: npm install react-dom --save
- babel包: npm install babel-standalone --save
<!-- 引入 -->
<script src="node_modules/react/umd/react.production.min.js"></script>
<script src="node_modules/react-dom/umd/react-dom.production.min.js"></script>
<script src="node_modules/babel-standalone/babel.min.js"></script>
<!-- jsx外部js引入要加上type="text/babel" -->
<script src="./index.js" type="text/babel"></script>
React --根DOM节点
- 页面中需要有一个div容器,容器中的内容都会被React Dom所管理。
- 这个容器叫做根DOM节点
- 注意:通常React 开发应用时一般只会定义一个根节点。
React --JSX
- JSX=javascript xml就是Javascript和XML结合的一种格式。是JavaScript 的语法扩展。 React推荐在 React 中使用 JSX 来描述用户界面。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析。
jsx优点:
-
JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
-
它是类型安全的,在编译过程中就能发现错误。
-
使用 JSX 编写模板更加简单快速。
React 开发环境搭建
- 编写React渲染组件ReactDOM.render()
- ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入页面指定的 根DOM 节点。
- 多个 HTML 标签,需要使用一个 父 元素包裹
- 使用圆括号包裹养成好习惯
<body>
<div id="demodiv"></div>
<script type="text/babel">
//创建虚拟DOM元素对象
let text = "变量";
let html = (
<div>
<p>你好</p>
<hr/>
<h1>{text}</h1>
</div>
) //把虚拟DOM渲染到真实DOM中
ReactDOM.render(html, document.getElementById("demodiv"))
</script>
</body>
Jsx中的HTML标签必须按照w3c规范来写 标签必须闭合
注释
- 注释/内容/ html标签内注释{/* 最外层有花括号*/}
JSX中使用模板语法
- 可以任意地在 JSX 当中使用 JavaScript表达式,在 JSX 当中的表达式要包含在大括号里。
JSX中使用表达式
<script type="text/babel">
let obj={
text:"点我去百度",
ahref:"http://www.baidu.com"
}
//创建虚拟DOM元素对象
let html = (
<div>
<a href={obj.ahref}>{obj.text}</a>
</div>
) //把虚拟DOM渲染到真实DOM中
ReactDOM.render(html, document.getElementById("demodiv"))
</script>
JSX独立文件使用
- React JSX 代码可以放在一个独立文件上创建一个 index.js 文件
- 页面中引用注意script中的type属性
<script src="./index.js" type="text/babel"></script>
逻辑判断 - 三目运算 :
<script type="text/babel">
var bool=true
//{表达式} 大括号中写表达式内容
let html = (
<div>
{bool?'真':'假'}
</div>
) //把虚拟DOM渲染到真实DOM中
ReactDOM.render(html, document.getElementById("demodiv"))
</script>
渲染数组 :
let text=[
<h1>1111</h1>,
<h1>2222</h1>,
<h1>3333</h1>
]
//创建虚拟DOM元素对象
let html = (
<div>
{text}
</div>
) //把虚拟DOM渲染到真实DOM中
ReactDOM.render(html, document.getElementById("demodiv"))
JSX绑定属性
- Jsx中也可以使用大括号来定义以 JavaScript 表达式为值的属性:
<img src={user. Url} />
- 切记使用了大括号包裹的 JavaScript 表达式时就不要再到外面套引号了。JSX 会将引号当中的内容识别为字符串而不是表达式。
定义样式 :
- 定义样式对象, 以style属性引用一个对象
- 样式名以驼峰命名法表示, 如text-align需写成textAlign
- 默认像素单位是 px
<script type="text/babel">
let html = (
<div>
<h1 style={{color:"red",fontSize:"16px"}}>react修改行内样式</h1>
</div>
) //把虚拟DOM渲染到真实DOM中
ReactDOM.render(html, document.getElementById("demodiv"))
</script>
引用外部样式 :
- 引用外部样式时, 不要使用class作为属性名, 因为class是js的保留关键字。JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 className(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
<h3 className="hh">13213</h3>
JSX扩展知识点
- html的value属性要写成:defaultValue
- html的checked属性要写成:defaultChecked
- style 里面写对象
- class 写className
React 列表渲染
- 在实际应用中 数据展示 是最基本的功能。
- React中使用的map()进行列表的渲染
React 列表渲染–Keys
- Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此要给数组中的每一个元素赋予一个确定的标识。
- 一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串
// 循环遍历
let arr=[11,22,33,44,55,66,77,88,99];
let com=(
<ul>
{
arr.map((v,i)=>{
return (
<li>{v}</li>
)
})
}
</ul>
)
//写法二
//let newxh= arr.map((v,i)=>{
// return (
// <li>{v}</li>
// )
// })
// let com=(
// <ul>
// {
// newxh
// }
// </ul>
// )
ReactDOM.render(com,document.getElementById("demodiv"))
- return 换行会出错 可以给后边加上()在换行编写
完成点击变色的demo小练习
// 点击变色
let arr=[
{name:"haha0",age:10},
{name:"haha1",age:11},
{name:"haha2",age:12},
{name:"haha3",age:13},
{name:"haha4",age:14},
]
let index=-1
function fn(){
return (
<table border="1">
<tbody>
{
arr.map((v,i)=>{
return(
<tr key={i} onClick={()=>{index=i;render()}} style={{backgroundColor:i==index?'green':''}}>
<td>{v.name}</td>
<td>{v.age}</td>
</tr>
)
})
}
</tbody>
</table>
)
}
//封装 点击后调用起到重新渲染作用
function render(){
ReactDOM.render(fn(),document.querySelector("#demodiv"))
}
render()
对象如何取值
- 对象取值有两种方式 1.用点的方式 2.用[]来取值
- 在实际项目中一般使用点,会方便许多,但是如果key是变量的话就不能使用点了,js会理解变量为对象的key值,造成混淆
- object.keys()返回一个数组类型 值是方法中对象的键(key)
- Object.values()返回一个数组类型 值是方法中对象的值(value)
- Object.entries()返回一个数组类型 值是方法中对象的键和值
//遍历对象
var obj={
name:"haha",
age:18,
sex:"男"
}
// function fn(){
// return Object.values(obj).map((v,i)=>{
// return (
// <h3 key={i}>{v}</h3>
// )
// })
// }
function fn(){
return Object.keys(obj).map((v,i)=>{
return (
<h3 key={i}>{obj[v]}</h3>
)
})
}
ReactDOM.render(fn(), document.getElementById("demodiv"))
模块与模块化
-
模块:向外提供特定功能的js文件,提高js的复用率简化编写提高循行效率
-
模块化:当应用的js都是用js模块编写的,这个应用就是模块化应用
组件与组件化
-
组件:用来实现页面局部功能效果的代码合集(html/css/js)简化复杂页面的编码,提高运行效率
-
组件化:当应用多使用组件的方式来完成,这个应用就是一个组件化应用
面向组件编程
组件基本概念
- 组件是React中非常重要的概念–是可以将UI切分成一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件。
定义自定义组件–函数组件/无状态组件
- 组件 首字母大写 首字母大写 首字母大写并且其后每个字母首字母大写
// 无状态组件 函数组件
// 首字母大写
function MyCom(){
return (
<h5>封装组件</h5>
)
}
let com=(
// 重复使用封装组件
// 用父元素包裹使用
<div>
<MyCom/><MyCom/><MyCom/><MyCom/>
</div>
)
ReactDOM.render(com, document.getElementById("demodiv"))
定义父子组件
- 通过创建多个组件来合成一个组件,即把组件的不同功能点进行分离。
function MyCom(){
return (
<h5>封装组件</h5>
)
}
//组件拼装
function Com(){
return (
<div>
<MyCom/>
</div>
)
}
ReactDOM.render(<Com/>, document.getElementById("demodiv"))
定义组件–ES6 class类组件
- 需要注意类名的首字母必须大写
- 一个组件类必须必须必须要实现一个 render 方法。这个方法必须要返回一个jsx元素。必须要用一个外层的jsx元素把所有的内容
//创建类组件
class MyCom extends React.Component{
render(){
return(
<div>
<h4>我是类组件</h4>
</div>
)
}
}
//父子类组件
//在创建一个类组件 在里边调用子组件即可(跟vue写法差不多)
props
Props传入参数
-
props 是组件对外的接口。使用props就可以从外部向组件内部进行数据传递 完成父组件传值给子组件
-
注意:Props对于使用它的组件来说,是只读的。一旦赋值不能修改。也就是说props的值是不可变的只能在渲染的时候传入无法动态赋值。
-
组件无论是使用无状态组件还是通过 类组件声明,都决不能修改自身的 props。
-
当一个组件被注入Props 值时,属性值来源于它的父级元素,从父级到子元素。
function MyCom(props){
return(
<div className="item">
<span>{props.title}</span>
<span>{props.content}</span>
</div>
)
}
function Fu(){
let el=arr.map((v,i)=>{
return (
<MyCom title={v.title} content={v.content}/>
)
})
return (
<div className="con">
{el}
</div>
)
}
//对象解构方法
function MyCom(props){
// 对象解构
let {title,content}=props.data
return(
<div className="item">
<span>{title}</span>
<span>{content}</span>
</div>
)
}
function Fu(){
let el=arr.map((v,i)=>{
return (
<MyCom data={v}/>
)
})
return (
<div className="con">
{el}
</div>
)
}
Props传入多个参数扩展
- 在参数较多的时候可以把Object类型进行传递
function MyCom(props){
return(
<div className="item">
<span>{props.name}</span>
<span>{props.age}</span>
<span>{props.sex}</span>
</div>
)
}
function Fu(){
let obj={
name:"小蜘蛛",
age:22,
sex:"变种人"
}
return (
//使用扩展运算符传入对象数据
<MyCom {...obj}/>
)
return (
<div className="con">
{el}
</div>
)
}
Props默认值
- 用||来进行判断 高版本有兼容性
//无状态组件的props默认值
function MyCom(props){
props.name=props.name||"我是默认值"
return (
<div>
<h4>我是一个组件---{props.name}</h4>
</div>
)
}
Props默认值-defaultProps
- 有些时候, 我们需要设置一些默认属性, 在父级组件未向子级组件传递数据时, 填充默认值。defaultProps
function MyCom(props){
return (
<div>
<h4>我是一个组件---{props.name}</h4>
</div>
)
}
//新版本写法
MyCom.defaultProps={
name:"我是默认值"
}
Props 验证
- 15.5之前Props 验证使用 propTypes,它可以保证我们在应用组件的时候可以正确的传递值
- 注意:自Reatc v15.5起,Reatc.PropTypes已移入另一个包中,请使用prop-types库代替
- 新版验证方式–注意要引用 prop-types 库
- 下载 npm install --save prop-types
- 引入
<script src="../node_modules/react/umd/react.development.js"></script>
<script src="../node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="../node_modules/babel-standalone/babel.min.js"></script>
<script src="../node_modules/prop-types/prop-types.js"></script>
function MyCom(props){
return (
<div>
<h4>我是一个组件---{props.name}</h4>
</div>
)
}
//props验证
//1.引用验证js包
//2.设置验证
MyCom.propTypes={
//验证必须是字符串 isRequired不能为空
//注意:单词大小写
name:PropTypes.number.isRequired
}
ReactDOM.render(<MyCom name={1}/>,document.getElementById("demodiv"))
组件–class类定义方式 : 传入参数
- 使用this.props.xx 接收值
//创建类组件
class MyCom extends React.Component{
render(){
return(
<div>
<h4>我是类组件的props:{this.props.name}</h4>
</div>
)
}
}
ReactDOM.render(<MyCom name="小蜘蛛"/>, document.getElementById("demodiv"))
- 类方式创建使用defaultPropos进行默认值设置两种方式
//创建类组件
class MyCom extends React.Component{
render(){
return(
<div>
<h4>我是类组件的props:{this.props.name}</h4>
</div>
)
}
}
//默认值
MyCom.defaultProps={
name:"我是默认值"
}
ReactDOM.render(<MyCom/>, document.getElementById("demodiv"))
验证
//创建类组件
class MyCom extends React.Component{
render(){
return(
<div>
<h4>我是类组件的props:{this.props.name}</h4>
</div>
)
}
}
//验证
MyCom.propTypes={
name:PropTypes.number
}
ReactDOM.render(<MyCom name={1}/>, document.getElementById("demodiv"))
React 列表渲染props传递进来的数据
let arr=[
{title:"千手斗罗",content:"唐三"},
{title:"柔骨斗罗",content:"小舞"},
{title:"白虎斗罗",content:"戴沐白"},
{title:"幽冥斗罗",content:"朱竹清"},
{title:"食神斗罗",content:"奥斯卡"},
{title:"九彩斗罗",content:"宁荣荣"},
{title:"凤凰斗罗",content:"马红俊"}
]
var MyCom=(props)=>{
var el=[]
props.data.forEach((v,i)=>{
el.push((
<li>
<span>{v.title}</span>
<span>{v.content}</span>
</li>
))
})
return (
<div>
<h4>props数组传值</h4>
<ul>
{el}
</ul>
</div>
)
}
ReactDOM.render(<MyCom data={arr}/>,document.getElementById("demodiv"))
小练习
- 基于上一个列表渲染的例子完成点击隐藏点击显示效果
let arr=["亚索","盲僧","剑圣","提姆"]
// 设置变量 用于判断列表的显示隐藏
let bool=true
//创建子组件 接收arr数组用于遍历
var Zi=(props)=>{
return (
<ul style={{display:bool?'block':'none'}}>
{
props.data.map((v,i)=>{
return (
<li key={i}>{v}</li>
)
})
}
</ul>
)
}
// 创建父组件
var MyCom=()=>{
return (
// 设置点击事件 调用子组件并传值
<div>
<h3 onClick={()=>{bool=!bool;render()}}>LOL英雄列表</h3>
<Zi data={arr}/>
</div>
)
}
//重复渲染 否则不会显示内容
function render(){
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
}
render()
State 状态机
- 在react中开发者只需要关心数据。数据改变页面就会发生改变
- 数据等同于状态。状态改变-页面绑定的数据就由react进行改变
- 组件被称之为“状态机”,视图与状态一一对应
使用–初始化状态
- 使用状态必须先初始化:this.state={}
constructor–super
- ES6的继承规则得知,不管子类写不写constructor,在new实例的过程都会给补上constructor。
- 可以不写constructor,一旦写了constructor,就必须在此函数中写super()super调用父类的构造方法,此时组件才有自己的this,在组件的全局中都可以使用this关键字,
- 否则如果只是constructor 而不执行 super() 那么以后的this都是错的!!!super()继承父组件的 this
super(props)
- 当想在constructor中使用this.props的时候,super需要加入(props),
- 此时用props也行,用this.props也行,他俩都是一个东西。(不过props可以是任意参数,this.props是固定写法)
使用–读取状态
- 读取状态:this.state.key1
render(){
//读取数据
return <div>{this.state.key1}</div>
}
修改state
- 更新状态—》组件界面更新:this.setState({})
- 小知识:setState()是异步的会自动触发render函数的重新渲染
fn=()=>{
//更新state
this.setState({
key1:"newkey1"
})
}
State–完整使用
class MyCom extends React.Component{
//使用this.state.xxx初始化数据
constructor(props){
super(props)
this.state={
name:"小蜘蛛",
age:18,
sex:"男"
}
}
fn=()=>{
// 修改state
//setState()是异步的会自动触发render函数的重新渲染
this.setState({
name:"大蜘蛛"
})
//利用回调函数解决setState()异步
// this.setState({
// name:"大蜘蛛"
// },()=>{
// console.log(this.state.name);
// })
}
render(){
return(
<div>
<h3>我是一个组件---{this.state.name}---{this.state.age}</h3>
<button onClick={this.fn}>点我修改name</button>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
扩展
- 插入字符串标签
- 使用react给我们提供的方法
- dangerouslySetInnerHTML = {{ __html:你要插入的字符串 }}
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={
text:"<h1>我是一个h1标签</h1>"
}
}
render(){
return(
//解析字符串标签方法
<div dangerouslySetInnerHTML={{__html:this.state.text}}>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
state显示隐藏小练习
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={
bool:false
}
}
fn=()=>{
this.setState({
bool:!this.state.bool
})
}
render(){
return(
<div>
<button onClick={this.fn}>点我切换内容显示隐藏</button>
<ul style={{display:this.state.bool?"block":"none"}}>
<li>争时心要静,</li>
<li>急时人要稳。</li>
<li>闲时要开心,</li>
<li>怒时切记
<span>心中坐把刀</span>!
</li>
</ul>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
ref属性
-
React提供的这个ref属性(不能在无状态组件上使用 ref 属性,因为它们没有实例)表示为对组件真正实例的引用其实就是ReactDOM.render()返回的组件实例
-
ReactDOM.render()渲染组件时返回的是组件实例;而渲染dom元素时,返回是具体的dom节点。
-
一句话总结:标识组件内部的元素
ref属性–使用
- React的ref有3种用法:
- 字符串(官方不推荐使用)
- 最早的ref用法。使用this.refs.xxx来进行访问
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={}
}
fn=()=>{
console.log(this.refs.demoref.value);
}
render(){
return(
<div>
<!-- 使用字符串方法定义ref -->
<input type="text" ref="demoref"/>
<hr/>
<button onClick={this.fn}>点我获取输入框的值</button>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
- 回调函数(官方推荐)
- 回调函数就是在dom节点或组件上挂载函数,函数的入参是dom节点,达到的效果与字符串形式是一样的,都是获取其引用。
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={}
}
fn=()=>{
console.log(this.cs.value);
}
render(){
return(
<div>
<!-- 使用回调函数方法定义ref cs自定义变量名-->
<input type="text" ref={(cs)=>{this.cs=cs}}/>
<button onClick={this.fn}>按钮</button>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
- React.createRef() (React16.3新提供)
- 在React 16.3版本后,使用此方法来创建ref。将其赋值给一个变量,通过ref挂载在dom节点或组件上该ref的current属性将能拿到dom节点或组件的实例
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={}
//创建
this.myref=React.createRef()
}
fn=()=>{
// 通过ref挂载在dom节点或组件上该ref的current属性将能拿到dom节点或组件的实例
console.log(this.myref.current.value);
}
render(){
return(
<div>
<input type="text" ref={this.myref}/>
<button onClick={this.fn}>点我获取输入框数据</button>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
React受控组件
React 表单
- React负责渲染表单的组件。同时仍然控制用户后续输入时所发生的变化。值是来自于state控制的 输入表单元素称为“受控组件”。
React组件生命周期
- 每个组件都包含“生命周期方法”,可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法
- React 生命周期分为三种状态:
- 挂载阶段
- constructor()中完成了React数据的初始化,
- componentWillMount()一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。
- componentDidMount()
- 组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
- componentWillUnmount在组件从 DOM 中移除之前立刻被调用。
- ReactDOM.unmountComponentAtNode(document.getElementById(“demodiv”));//卸载组件
- 更新阶段
-
componentWillReceiveProps (nextProps)
-
在组件接收到一个新的prop时被调用。
-
shouldComponentUpdate 判定组件是否要更新html 主要用于性能优化(部分更新)唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
-
componentWillUpdate()
-
组件即将更新html时候调用shouldComponentUpdate返回true以后,组件进入重新渲染的流程
-
componentDidUpdate 在组件完成更新后立即调用。
-
render()函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
- 卸载阶段
React 生命周期纵览
- 生命周期的方法有:
- componentWillMount 组件渲染之前调用
- componentDidMount 组件渲染之后调用在第一次渲染后调用
- componentWillReceiveProps在组件接收到一个新的prop时被调用。这个方法在初始化render时不会被调用。
- shouldComponentUpdate 判定组件是否要更新html
- componentWillUpdate组件即将更新html时候调用
- componentDidUpdate 在组件完成更新后立即调用。componentWillUnmount在组件从 DOM 中移除之前立刻被调用。
- ReactDOM.unmountComponentAtNode(document.getElementById(“demodiv”));//卸载组件
事件处理—阻止默认行为
- React中阻止默认行为使用preventDefault()
事件处理—修改this指向
- 方式1:通过bind方法进行原地绑定,从而改变this指向
- 方式2:通过创建箭头函数
- 方式3:在constructor中提前对事件进行绑定
- 方式4:将事件调用的写法改为箭头函数的形式
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={
text:"哈哈"
}
//提前给函数绑定this
this.fnd=this.fnd.bind(this)
}
// 方式2:通过创建箭头函数
fn=()=>{
this.setState({
text:"嘿嘿"
})
}
// 方式1:通过bind方法进行原地绑定,从而改变this指向
funb=()=>{
this.setState({
text:"小蜘蛛"
})
}
//方式4:将事件调用的写法改为箭头函数的形式
fna(){
this.setState({
text:"黑蜘蛛"
})
}
// 方式3:在constructor中提前对事件进行绑定
fnd(){
this.setState({
text:"红蜘蛛"
})
}
render(){
return(
<div>
<h3>this指向</h3>
<p>{this.state.text}</p>
<button onClick={this.fn}>点我1</button>
<button onClick={this.funb.bind(this)}>点我2</button>
<button onClick={this.fnd}>点我3</button>
<button onClick={()=>{this.fna()}}>点我4</button>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
事件处理—传递参数
- 向事件处理函数中传递参数。
- 方式1(推荐):通过 bind 的方式进行传递
- 方式2:通过箭头函数传递。注意使用箭头函数调用事件对象必须显式的进行传递
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={}
}
fn(a,b){
console.log(`${a}-----${b}`);
}
render(){
return(
<div>
<button onClick={this.fn.bind(this,1,2)}>点我方式1传参</button>
<button onClick={()=>{this.fn(3,4)}}>点我方式2传参</button>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
React状态提升
- React中的状态提升概括来说,就是多个组件需要反映相同的变化数据,提升到它们最近的父组件上.在父组件上改变这个状态然后通过props分发给子组件.
- 状态提升也就是:
- 两个子组件需要利用到对方的状态的话,那么这个时候我们就需要使用到状态提升
React条件渲染
-
开发中,创建不同的组件来封装各种你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。
-
React 中的条件渲染和 JavaScript 中的一致,使用 JavaScript 操作符 if 或条件运算符来创建表示当前状态的元素,然后让 React 根据它们来更新 UI。
条件渲染-if 语句
- 在React中使用if语句条件渲染是最简单的,但是注意jsx中不允许有if
class MyComa extends React.Component{
constructor(props){
super(props)
this.state={}
}
render(){
return(
<div>aaa</div>
)
}
}
class MyComb extends React.Component{
constructor(props){
super(props)
this.state={}
}
render(){
return(
<div>bbb</div>
)
}
}
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={
bool:true
}
}
fn=()=>{
if(this.state.bool){
return <MyComa/>
}else{
return <MyComb/>
}
}
fun=()=>{
this.setState({
bool:!this.state.bool
})
}
render(){
return(
<div>
{this.fn()}
<button onClick={this.fun}>点我切换</button>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
- 三目运算跟Javascript用法一致
- 条件渲染-&& (this.state.bool==true&&<组件/>) 此方法只能渲染一个组件
todulist小练习
- 添加 修改 删除
class MyCom extends React.Component{
constructor(props){
super(props)
this.state={
arr:[
{name:"EZ"},
{name:"VN"},
{name:"NOC"},
{name:"MF"},
]
}
}
//便捷方法xiu
showlist=()=>{
return this.state.arr.map((v,i)=>{
return (
<li key={i}><p style={{display:'inline-block'}}>{v.name}</p> <button onClick={this.del.bind(this,i)}>删除</button><button onClick={this.xiu.bind(this,i)}>修改</button></li>
)
})
}
// 添加
add(){
// window弹窗
// prompt("修改操作")
let inputval=this.content.value
// 判断输入框的值为空不执行
if(inputval==''){
return
}
// 旧语法
// let newarr=this.state.arr
// newarr.push({name:inputval})
// this.setState({
// arr:newarr
// })
// 新语法
let newarr=[...this.state.arr,{name:inputval}]
this.setState({
arr:newarr
})
// 添加后清空input框值
this.content.value=''
// 添加一次后自动再次获取input焦点 不用再次点击input框
this.content.focus()
}
//删除
del(num){
let newarr=this.state.arr
newarr.splice(num,1)
this.setState({
arr:newarr
})
}
// 修改
xiu(up){
let newarr=this.state.arr
let pt=prompt("输入修改内容")
//判断修改框是否有内容 无则不执行修改
if(pt){
newarr[up].name=pt
this.setState({
arr:newarr
})
}
}
render(){
return(
<div>
<h2>LOL英雄管理</h2>
<input type="text" ref={(con)=>{this.content=con}}/><button onClick={this.add.bind(this)}>添加</button>
<hr/>
<ul>
{this.showlist()}
</ul>
</div>
)
}
}
ReactDOM.render(<MyCom/>,document.getElementById("demodiv"))
删除修改待补充
React应用-脚手架
- 在企业级开发中是没有人使用传统引用的方式来进行项目开发的
- 使用npm安装各种插件非常方便的可以进行项目的开发与运行
-
create-react-app安装
-
npm install -g create-react-app 安装脚手架
-
create-react-app --version 查看版本
-
create-react-app 项目名 创建项目
-
cd 项目名 切换到创建好的项目中
-
npm start 启动运行
React脚手架使用目录结构
- public静态资源文件夹:里面的index.html,是整个react项目最终打包的index入口页面的项目模板。但是和我们的代码编写无关,和最终的页面展示是相关的。
- 静态资源:可以直接在项目中访问
- 比如public文件夹的内容想访问直接在浏览器中输入
- http://localhost:3000/logo192.png(文件名)即可查看
- src文件夹:是我们编写代码的地方。
- src/index.js:是整个项目的入口js文件。
- src/app.js:是被index引入的一个组件,也用一个js文件表示。
- src/index.css:index.js中的的样式文件。
- src/app.css:是app.js的样式文件。
- logo.svg:是一个svg的图片文件。用于界面展示使用。
- 但是原生的结构很不适合开发所以需要对格式进行修改
- 创建assets 静态资源文件夹
- components组件文件夹
- pages页面文件夹
- styles css样式文件夹
React脚手架使用-创建组件
- ES6 class 来定义一个组件:
import React, { Component } from 'react'
export default class home extends Component {
render() {
return (
<div>
</div>
)
}
}
- 在使用的地方先引用 在使用
React脚手架使用-配置路径别名
-
遇到相对路径层级很深的时候,想“…/…/…/”阅读取来也不方便
-
下载 :npm install react-app-rewired -D
-
修改启动命令在package.json 中修改scripts为:“start”: “react-app-rewired start”
-
在根目录文件下,新建 “config-overrides.js” 文件配置
const path = require('path');
function resolve(dir) {
return path.join(__dirname, '.', dir)
}
module.exports = function override(config, env) {
config.resolve.alias = {
'@': resolve('src'),
"com":resolve("src/components")
}
return config; }
重新启动即可
React脚手架使用-多行标签
- 可以在外层加一个父元素
- 方式1 传统标签(这种方式会在页面中新增DOM节点)
import React, { Component } from 'react'
import Pone from "./pone"
export default class home extends Component {
render() {
return (
<div>
大傻子
{/* 1.组件的使用 ---> 引用 ---> 使用 */}
<Pone/>
</div>
)
}
}
- 方法2 Fragment不在DOM中增加额外节点或者使用<></>空标记
import React, { Component } from 'react'
export default class pone extends Component {
render() {
return (
//空标签写法
<>
{/* 2.组件的多行标签 空标签 起到了多行标签的包裹作用 但是不参与渲染*/}
<span>大蜘蛛</span>
</>
//Fragment写法
<Fragment>
{/* 2.组件的多行标签 空标签 起到了多行标签的包裹作用 但是不参与渲染*/}
<span>大蜘蛛</span>
</Fragment>
)
}
}
React脚手架使用–组件样式设置
- 不建议使用—会造成全局污染
import React, { Component } from 'react'
// 引用css样式 在pattern.css(组件名自定义后缀css)组件中添加要设置的样式代码
import "./pattern.css"
export default class pattern extends Component {
render() {
return (
<div>
{/* 样式的使用 */}
<h3>修改样式</h3>
</div>
)
}
}
- styled-components 建议使用这种写法
- styled-components 是一个常用的 JavaScript里写CSS 类库。和所有同类型的类库一样,通过 js 赋能解决了原生 css 所不具备的能力,比如变量、循环、函数等。
- CSS代码时都有哪些特点:
- 全局污染 - CSS的选择器是全局生效的,所以在class名称比较简单时,容易引起全局选择器冲突,导致样式互相影响。
- 命名混乱 - 因为怕全局污染,所以日常起class名称时会尽量加长,这样不容易重复,但当项目由多人维护时,很容易导致命名风格不统一。
- 样式重用困难 - 有时虽然知道项目上已有一些相似的样式,但因为怕互相影响,不敢重用。
- 代码冗余 - 由于样式重用的困难性等问题,导致代码冗余。
- 在CSS的进化历史上,出现过各种各样的框架致力于解决以上的问题:
- SASS, LESS - 提供了变量、简单函数、运算、继承等,扩展性、重用性都有了很大的提升,解决了一些样式重用冗余的问题,但是对于命名混乱问题的效果不大。
- CSS Modules - 模块化CSS,将CSS文件以模块的形式引入到JavaScript里,基本上解决了全局污染、命名混乱、样式重用和冗余的问题,但CSS有嵌套结构的限制(只能一层),也无法方便的在CSS和JavaScript之间共享变量。
- 下载npm install --save styled-components
- vscode插件:vscode-styled-components
styled-components基本使用
- 在需要使用的组件文件夹中创建styled文件夹并在其中创建js文件
- 注意组件首字母必须大写不然无法识别
import style from "styled-components"
//注意 单词首字母必须大写 其后单词首字母也要大写 便于辨识
//可以使用嵌套写法
export let PatternStyle=style.div`
h2{
color:red;
span{
font-weight: bold;
color: darkorange;
}
}
`
- 在组件中引用样式js文件 并使用
import React, { Component } from 'react'
// 引用css样式
// import "./pattern.css"
//styled-components的样式引用方式 需要结构
import {PatternStyle} from "./pattern/patternStyle.js"
export default class pattern extends Component {
render() {
return (
<div>
<PatternStyle>
<h3>styled-components样式使用</h3>
</PatternStyle>
{/* 样式的使用 */}
<h3>修改样式</h3>
</div>
)
}
}
React脚手架使用-使用图片
- 把图片放到public文件夹中 直接使用图片名使用
- 不在public文件夹中 .使用require引用
- require引用方式中只能使用字符串不能使用变量
- 不在public文件夹中使用 导入图片路径:
import ImgA from "../assets/98.jpg"
<img src={ImgA} alt=""/>
React脚手架使用-传递值
- 使用props传递值
import React, { Component } from 'react'
//props验证
import PropTypes from 'prop-types'
export default class proposdemo extends Component {
static propTypes = {
//props验证
title: PropTypes.number
}
render() {
return (
<div>
<h1>组件传值{this.props.title}</h1>
</div>
)
}
}
- 在使用的地方先引用 在使用 在传递
<Proposdemo title={123}/>
state
React脚手架使用-修改值
- 使用state修改值
- 属性传值
- 使用两种方式进行传值给函数动态的改变state修改内容
- 在函数中创建接收数据的形参
- 使用es6的箭头函数传值(因为不能使用this.fun(“参数”)这种写法会在没有点击的时候就自动调用函数)
推荐使用
- 使用两种方式进行传值给函数动态的改变state修改内容
- 使用bind()来进行传递
说明 这里用小练习演示state
- 父组件
import React, { Component } from 'react'
//引用父组件样式
import BottomStyled from "./bottomStyle.js"
//引用子组件
import Item from "./item"
export default class bottom extends Component {
//props验证
constructor(props){
super(props)
//state数据 此处用于传值
this.state={
arr:[
{title:"首页",imgurl:"img/ciwei.png",selectimgurl:"img/ciwei_hl.png"},
{title:"影视",imgurl:"img/dianying.png",selectimgurl:"img/dianying_hl.png"},
{title:"新闻",imgurl:"img/recommend.png",selectimgurl:"img/recommend_selected.png"},
{title:"播放",imgurl:"img/video.png",selectimgurl:"img/video_selected.png"},
{title:"阅读",imgurl:"img/yuedu.png",selectimgurl:"img/yuedu_hl.png"}
]
}
}
shoulist=()=>{
return this.state.arr.map((v,i)=>{
return (
//解构
<Item key={i} {...v} index={i}/>
//普通写法
// <Item key={i} title={v.title} imgurl={v.imgurl} selectimgurl={v.selectimgurl} index={i}/>
)
})
}
render() {
return (
//用样式组件包裹内容
<BottomStyled>
<footer>
{this.shoulist()}
</footer>
</BottomStyled>
)
}
}
- 父组件样式
//需要下载styled-components模块
import styled from "styled-components"
let BottomStyled=styled.div`
footer{
display: flex;
position: fixed;
bottom: 0;
width: 100%;
height: 60px;
background-color: pink;
}
`
//将样式暴露出去
export default BottomStyled
- 子组件
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from "./itemStyle.js"
export default class item extends Component {
//props验证
static propTypes = {
prop: PropTypes.string
}
constructor(){
super()
//state修改数据
this.state={
bool:true
}
}
//find方式进行传值 推荐使用
fn(mun){
console.log(mun);
this.setState({
bool:!this.state.bool
})
}
render() {
let {title,imgurl,selectimgurl,index}=this.props
return (
<Item>
<div onClick={this.fn.bind(this,index)}>
{this.state.bool? <img src={imgurl}/>: <img src={selectimgurl}/>}
<span>{title}</span>
</div>
</Item>
)
}
}
- 子组件样式
//需要下载styled-components模块
import styled from "styled-components"
let ItemStyled=styled.div`
flex:1;
div{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
span{
margin-top: 5px;
}
img{
width:30px;
height:30px
}
}
`
export default ItemStyled
forceUpdate()就是重新调用render渲染。有些变量不在state上,当时你又想达到这个变量更新的时候,刷新render;
- 写在函数体内 写法(this.forceUpdate())