1.网页依赖 Vue2:
<script src="https://unpkg.com/vue/dist/vue.js"></script>
Vue3:
<script src="https://unpkg.com/vue@next"></script>
React:
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
2.安装和脚手架 添加镜像和cnpm
npm config set registry registry.npm.taobao.org npm install -g cnpm --registry=registry.npm.taobao.org Vue2:
$ cnpm install vue
# 全局安装 vue-cli
$ cnpm install --global vue-cli
# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project
# 这里需要进行一些配置,默认回车即可
$ cd my-project
$ cnpm install
$ cnpm run dev
#打包
$ cnpm run build
Vue3:
$ cnpm install vue@next
# 全局安装 vue-cli
$ cnpm install -g @vue/cli
$ cnpm install -g @vue/cli-init
$ vue init webpack hello-vue3
# 这里需要进行一些配置,默认回车即可
#或者使用 vue create hello-vue3
# select vue 3 preset
$ cd hello-vue3
$ cnpm install
$ cnpm run dev
#打包
$ cnpm run build
React:
$ cnpm install -g create-react-app
$ create-react-app my-app
$ cd my-app/
安装依赖:cnpm install
启动项目:cnpm start
编译:cnpm run build
3.生命周期 Vue2:
beforeCreate :数据还没有挂载呢,只是一个空壳
created:这个时候已经可以使用到数据,也可以更改数据,在这里更改数据不会触发updated函数
beforeMount:虚拟dom已经创建完成,马上就要渲染,在这里也可以更改数据,不会触发updated
mounted:此时,组件已经出现在页面中,数据、真实dom都已经处理好了,事件都已经挂载好了
beforeUpdate:重新渲染之前触发,然后vue的虚拟dom机制会重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染
updated:数据已经更改完成,dom也重新render完成
beforeDestory:销毁前执行($destroy方法被调用的时候就会执行),一般在这里善后:清除计时器、清除非指令绑定的事件等等...
destroyed:组件的数据绑定、监听...都去掉了,只剩下dom空壳,这里也可以善后
常用的生命周期:created,mounted,beforeDestory
Vue3:
说明:由于Vue2中的beforeCreate和created生命周期的执行几乎与VUE3中的setup在同一时间执行,所以在Vue2中写在created和beforeCreate中的代码可以直接写在setup()方法中
总之:
beforeCreate()和created()构造函数都应该写在setup()函数中
修改了部分钩子函数的名称
BeforeDestroy变成了onBeforeUnmount,destroyed变成了onUnmounted
新增了两个调试用钩子函数
onRenderTracked,onRenderTriggered
React:
1) react初始化的顺序是,constructor然后是componentWillMount(相当于vue 的beforeMounted) 然后是 render渲染函数再然后是componentDidMount(相当于vue的mounted )
2) 更新的时候:componentWillUpdate、render、componentDidUpdate(跟vue一样 平时没在用到更新的钩子函数)
shouldComponentUpdate,其实在更新的时也会触发这个钩子函数,你不写默认就是true,数据改变会执行更新的钩子函数, 如果你要组织他更新的话就写这个钩子函数写成false,意思就是不让更新.
3) 销毁的时候: componentWillUnmount(vue的 话一般用beforeDestroy用来清除定时器等)
这里还有一个比较少用的钩子函数,
在父组件里面改变props传值的时候触发的:componentWillReceiveProps
例如,组件需要以props中的某个属性作为与服务器通信时的请 求参数,当这个属性值发生更新时,组件自然需要重新与服务器通信。 不难发现 componentWillReceiveProps非常适合做这个工作。
react16.4后使用了新的生命周期,使用getDerivedStateFromProps代替了旧的componentWillReceiveProps及componentWillMount。使用getSnapshotBeforeUpdate代替了旧的componentWillUpdate。
最常用的还是
componentDidMount(对vue2--mounted,vue3-onMounted).componentWillUnmount(对vue2--beforeDestroy,vue3-onBeforeUnMount)
//Class component
class MyReactComponent extends React.Component {
static getDerivedStateFromProps(props, state) {}
componentDidMount() {}
shouldComponentUpdate(nextProps, nextState) {}
getSnapshotBeforeUpdate(prevProps, prevState) {}
componentDidUpdate(prevProps, prevState) {}
componentWillUnmount() {}
render() {
return <div>Hello World</div>;
}
}
4.基本目录 vue2:
├── build/ # Webpack 配置目录
├── dist/ # build 生成的生产环境下的项目
├── src/ # 源码目录(开发都在这里进行)
│ ├── assets/ # 放置需要经由 Webpack 处理的静态文件
│ ├── components/ # 组件
│ ├── filters/ # 过滤器
│ ├── store/ # 状态管理
│ ├── routes/ # 路由
│ ├── services/ # 服务(统一管理 XHR 请求)
│ ├── utils/ # 工具类
│ ├── views/ # 路由页面组件
│ ├── app.js # 启动文件
│ ├── index.html # 静态基页
├── static/ # 放置无需经由 Webpack 处理的静态文件
├── .babelrc # Babel 转码配置
├── .eslintignore # (配置)ESLint 检查中需忽略的文件(夹)
├── .eslintrc # ESLint 配置
├── .gitignore # (配置)需被 Git 忽略的文件(夹)
├── package.json # (这个就不用多解释了吧)
├── package-lock.json # (以记录当前状态下实际安装的各个npm package的具体来源和版本号)
Vue3:
|-node_modules -- 所有的项目依赖包都放在这个目录下
|-public -- 公共文件夹
---|favicon.ico -- 网站的显示图标
---|index.html -- 入口的html文件
|-src -- 源文件目录,编写的代码基本都在这个目录下
---|assets -- 放置静态文件的目录,比如logo.pn就放在这里
---|components -- Vue的组件文件,自定义的组件都会放到这
---|App.vue -- 根组件,这个在Vue2中也有
---|main.ts -- 入口文件,因为采用了TypeScript所以是ts结尾
---|shims-vue.d.ts -- 类文件(也叫定义文件),因为.vue结尾的文件在ts中不认可,所以要有定义文件
|-.browserslistrc -- 在不同前端工具之间公用目标浏览器和node版本的配置文件,作用是设置兼容性
|-.eslintrc.js -- Eslint的配置文件,不用作过多介绍
|-.gitignore -- 用来配置那些文件不归git管理
|-package.json -- 命令配置和包管理文件
|-README.md -- 项目的说明文件,使用markdown语法进行编写
|-tsconfig.json -- 关于TypoScript的配置文件
|-yarn.lock -- 使用yarn后自动生成的文件,由Yarn管理,安装yarn包时的重要信息存储到yarn.lock文件中
React:
主要看 public 和 src 文件夹中的文件:
public 文件夹是公共文件夹,与src最大的区别就是不会被webpack加工,用于存放一些静态文件。
manifest.json ,扩展的配置文件
robots.txt,爬虫爬取时应该遵守的规则
index.html,整个项目的入口文件
其他文件都是图标
src 文件夹是项目存放源文件的文件夹,项目的主要内容都会存放在里面。
App.js, 项目的主组件
App.css,App组件的样式
App.test.js,测试文件
index.css,整个项目的全局样式
index.js,JS的入口文件
serviceWorker.js,用于使项目可以离线运行
5.入口App Vue2: main.js是我们的入口文件,主要作用是初始化vue实例并使用需要的插件。
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
App.vue是我们的主组件,所有页面都是在App.vue下进行切换的。可以理解为所有的路由router也是App.vue的子组件
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
Vue3+ts:
main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
React:
// index.js
import React from 'react';
// ReactDom用于页面渲染
import ReactDOM from 'react-dom';
// index.css用于处理页面样式
import './index.css';
// App 是一个组件
import App from './App';
import * as serviceWorker from './serviceWorker';
// 用ReactDOM.render来将元素渲染到页面中
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
6.基本组件写法 Vue2:
<template>
<h1>Hello World</h1>
</template>
<script>
export default {
name: "MyVueComponent",
};
</script>
Vue3+ts:
<template>
<div>
<h2>vue3新语法</h2>
<div>{{girl}}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "App",
setup() {
const girl = ref('番茄女孩');
return {
girl
};
},
});
</script>
比较齐全的 Vue3+ts
React:
// Class component
class MyReactComponent extends React.Component {
render() {
return <h1>Hello world</h1>;
}
}
// Function component
function MyReactComponent() {
return <h1>Hello world</h1>;
}
7.Props Vue2:
<template>
<h1>Hello {{ name }}</h1>
</template>
<script>
export default {
name: "MyVueComponent",
props: {
name: {
type: String,
required: true,
},
mark: {
type: String,
default: "!",
},
},
};
</script>
...
<MyVueComponent name="World" />
Vue3+ts:
setup有两个参数 props 和 context
props:接受父组件传的值
context:vue3.x里面没有this,提供了一个context上下文属性,你可以通过这个属性去获取进行 一些 vue2.x 用this实现的操作
<template>
<h1>Hello {{ name }}</h1>
</template>
<script lang="ts">
export default {
name: "MyVueComponent",
props: {
name: {
type: String,
required: true,
default: "!",
},
},
setup(props,context){
console.log(props.name)
}
};
</script>
React:
function MyReactComponent(props) {
const { name, mark } = props;
return <h1>Hello {name}{mark}</h1>;
}
MyReactComponent.propTypes = {
name: PropTypes.string.isRequired,
mark: PropTypes.string,
}
MyReactComponent.defaultProps = {
mark: '!',
}
...
<MyReactComponent name="world">
8.事件绑定 Vue2,Vue3:
<template>
<button @click="save()">Save</button>
</template>
<script>
export default {
methods: {
save() {
console.log("save");
},
},
};
</script>
React:
//Class component
class MyReactComponent extends React.Component {
save = () => {
console.log("save");
};
render() {
return <button onClick={this.save}>Save</button>;
}
}
//Function component
function MyReactComponent() {
const save = () => {
console.log("save");
};
return <button onClick={save}>Save</button>;
}
9.自定义事件(子组件调用父组件函数,可以传参数) Vue2,Vue3:
<template>
<button @click="deleteItem()">{{item.name}}</button>
</template>
<script>
export default {
name: "my-item",
props: {
item: Object,
},
methods: {
deleteItem() {
this.$emit("delete", this.item);
},
},
};
</script>
...
<template>
<MyItem :item="item" @delete="handleDelete" />
</template>
<script>
export default {
components: {
MyItem,
},
methods: {
handleDelete(item) { ... }
},
};
</script>
React:
function MyItem({ item, handleDelete }) {
return <button onClick={() => handleDelete(item)}>{item.name}</button>;
/*
* 应用useCallback钩子来防止在每次渲染时生成新的函数。
*
* const handleClick = useCallback(() => handleDelete(item), [item, handleDelete]);
*
* return <button onClick={handleClick}>{item.name}</button>;
*/
}
...
function App() {
const handleDelete = () => { ... }
return <MyItem item={...} handleDelete={handleDelete} />
}
10.State(Data)和Change-State(Change Data) Vue2:
<template>
<div>
<span>{{count}}</span>
<button @click="increaseCount()">Add</button>
</div>
</template>
<script>
export default {
data() {
return { count: 0 };
},
methods: {
increaseCount() {
this.count = this.count + 1;
},
},
};
</script>
Vue3+ts:
ref 声明基础数据类型
创建一个响应式的基础类型,只能监听number、String、boolean等简单的类型,该属性一旦发生更改,都会被检测到。
<template>
<div>{{count}}</div> // 1
</template>
import {ref} from "vue"
setup(){
const count =ref(0)
count.value++ //必须要加.value
return{
count //一定要return 出去
}
}
reactive 声明响应式数据对象
<template>
<div>{{count.name}}</div> // 857
</template>
import {reactive} from "vue"
setup(){
const count =reactive({
name:'369'
})
count.name='857'
return{
count
}
}
React:
//Class component
class MyReactComponent extends React.Component {
state = {
count: 0,
};
increaseCount = () => {
this.setState({ count: this.state.count + 1 });
// 在更新之前获取当前状态,以确保我们没有使用陈旧的值
// this.setState(currentState => ({ count: currentState.count + 1 }));
};
render() {
return (
<div>
<span>{this.state.count}</span>
<button onClick={this.increaseCount}>Add</button>
</div>
);
}
}
//functiom component
function MyReactComponent() {
const [count, setCount] = useState(0);
const increaseCount = () => {
setCount(count + 1);
// setCount(currentCount => currentCount + 1);
};
return (
<div>
<span>{count}</span>
<button onClick={increaseCount}>Add</button>
</div>
);
}
11.双向绑定 (仅Vue.js,语法糖,加入界面数据->data的变化) Vue2,Vue3
<template>
<input type="text" v-model="content" />
</template>
<script>
export default {
data() {
return { content: "" };
},
};
</script>
React:
function MyReactComponent() {
const [content, setContent] = useState("");
return (
<input
type="text"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
);
}
12.计算属性computed watch和computed各自处理的数据关系场景不同
watch擅长处理的场景:一个数据影响多个数据
computed擅长处理的场景:一个数据受多个数据影响
Vue2:
<template>
<div>{{displayName}}</div>
</template>
<script>
export default {
name: "display-name",
props: {
firstName: String,
lastName: String,
},
computed: {
displayName: function () {
return `${this.firstName} ${this.lastName}`;
},
},
};
</script>
...
<DisplayName firstName="Hello" lastName="World" />
Vue3+ts:
async setup() {
const data = reactive({
a: 10,
b: 20,
});
let sum = computed(() => data.a + data.b);
return { sum };
},
React:使用useMemo
function DisplayName({ firstName, lastName }) {
const displayName = useMemo(() => {
return `${firstName} ${lastName}`;
}, [firstName, lastName]);
return <div>{displayName}</div>;
}
...
<DisplayName firstName="Hello" lastName="World" />
13.watch Vue2:
<template>
<div>
<span>{{count}}</span>
<button @click="increaseCount()">Add</button>
</div>
</template>
<script>
export default {
data() {
return { count: 0 };
},
methods: {
increaseCount() {
this.count = this.count + 1;
},
},
watch: {
count: function (newCount, oldCount) {
localStorage.setItem("my_count", newCount);
},
},
};
</script>
Vue3+ts:
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
//...
}
)
React:使用useEffect
function MyReactComponent() {
const [count, setCount] = useState(0);
const increaseCount = () => {
setCount((currentCount) => currentCount + 1);
};
useEffect(() => {
localStorage.setItem("my_count", newCount);
}, [count]);
return (
<div>
<span>{count}</span>
<button onClick={increaseCount}>Add</button>
</div>
);
}
class Component 可以在下面两个函数设置,也可以借助第三方库
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
14.Children-and-Slot Slot
是对组件的扩展,通过slot插槽向组件内部指定位置传递内容,通过slot可以父子传参; 开发背景(slot出现时为了解决什么问题): 正常情况下,hello world在组件标签Child中的span标签会被组件模板template内容替换掉,当想让组件标签Child中内容传递给组件时需要使用slot插槽。
Vue2,Vue3
<template>
<div>
<slot />
</div>
</template>
<script>
export default {
name: "my-vue-component",
};
</script>
...
<MyVueComponent>Hello World</MyVueComponent>
React:
function MyReactComponent({ children }) {
return <div>{children}</div>;
}
...
<MyReactComponent>Hello World</MyReactComponent>
15.渲染HTML Vue2,Vue3
<template>
<div v-html="html"></div>
</template>
<script>
export default {
data() {
return {
html: "<pre>...</pre>",
};
},
};
</script>
React:
function MyReactComponent() {
return <div dangerouslySetInnerHTML={{ __html: "<pre>...</pre>" }} />;
}
16.条件渲染 Vue2,Vue3
<template>
<div>
<!--v-show: 总是渲染,但根据条件更改CSS-->
<span v-show="loading">Loading...</span>
<div>
<div v-if="loading">is loading</div>
<div v-else>is loaded</div>
</div>
</div>
</template>
<script>
export default {
data() {
return { loading: true };
},
};
</script>
React:
function MyReactComponent() {
const [isLoading, setLoading] = useState(true);
return (
<div>
{isLoading && <span>Loading...</span>}
{isLoading ? <div>is loading</div> : <div>is loaded</div>}
</div>
);
}
17.列表渲染 Vue2,Vue3
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{item.name}}: {{item.desc}}
</li>
</ul>
</template>
<script>
export default {
props: {
items: Array,
},
};
</script>
React:
function MyReactComponent({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}: {item.desc}
</li>
))}
</ul>
);
}
18.Ref Vue2,Vue3
<template>
<input ref="input" type="text" v-model="content" />
</template>
<script>
export default {
name: "autofocus-input",
data() {
return { content: "" };
},
mounted() {
this.$refs.input.focus();
},
};
</script>
React:
//Class component
class AutofocusInput extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
}
state = {
content: "",
};
componentDidMount() {
this.ref.current.focus();
}
setContent = (e) => {
this.setState({ content: e.target.value });
};
render() {
return (
<input
ref={this.ref}
type="text"
value={this.state.content}
onChange={this.setContent}
/>
);
}
}
//Function component
function AutofocusInput() {
const [content, setContent] = useState("");
const ref = useRef(null);
useEffect(() => {
if (ref && ref.current) {
ref.current.focus();
}
}, []);
return (
<input
ref={ref}
type="text"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
);
}
19.vuex,redux 为什么不用localstorage可以代替vuex?
1).区别:vuex存储在内存,localstorage(本地存储)则以文件的方式存储在本地,永久保存;sessionstorage( 会话存储 ) ,临时保存。localStorage和sessionStorage只能存储字符串类型,对于复杂的对象可以使用ECMAScript提供的JSON对象的stringify和parse来处理
2).应用场景:vuex用于组件之间的传值,localstorage,sessionstorage则主要用于不同页面之间的传值。
3).永久性:当刷新页面(这里的刷新页面指的是 --> F5刷新,属于清除内存了)时vuex存储的值会丢失,sessionstorage页面关闭后就清除掉了,localstorage不会。
注:很多同学觉得用localstorage可以代替vuex, 对于不变的数据确实可以,但是当两个组件共用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage,sessionstorage无法做到,原因就是区别1。
vue2:
const store = new Vuex.Store({
state:{
loading: false,
todoList : [
{id:1,name:'11'},
{id:2,name:'22'},
{id:3,name:'33'},
],
num: 0,
},
mutations:{
setNumIs5(state){
state.num = 5
},
setNumIsWhat(state,payload){
state.num = payload.num
}
},
actions:{ <----- 增加actions属性
setNum(content){ <----- 增加setNum方法,默认第一个参数是content,其值是复制的一份store
return new Promise((resolve)=>{ <----- 返回一个promise,我们模拟一个异步操作,1秒后修改num为5
setTimeout(()=>{
content.commit('setNumIs5')
resolve()
},1000)
})
}
}
})
async mounted() {
console.log('旧值---'+this.$store.state.num);
await this.$store.dispatch('setNum') <----- actions使用dispatch进行触发,就像mutation使用commit触发一样
console.log('新值---'+this.$store.state.num);
},
Vue3与Vue2不同之处:
//创建实例的方式改变,Vue2.x为new Store , Vue3.x为createStore
//Vue2.x 中创建 store 实例
export default new Vuex.Store({
// ...
})
//Vue3.x
import Vuex from 'vuex'
export default Vuex.createStore({
state: {
count: 0
},
mutations: {
ADD (state) {
state.count++
}
},
actions: {
add ({ commit }) {
commit('ADD')
}
},
modules: {
}
})
React:
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
//第一步
import {Provider} from 'react-redux';//引入react-redux
import store from './store/index';
//作用:把store提供给<Provider></Provider>所有的组件实现store共享
const App=(
<Provider store={store}>
<TodoList/>
{/*这里面的组件都有能力获取store里的数据*/}
{/*<A></A>*/}
{/*<B></B>*/}
</Provider>
);
//
// ReactDOM.render(<TodoList />, document.getElementById('root'));
ReactDOM.render(App, document.getElementById('root'));
//TodoList
import React,{Component} from 'react';
//第二步
import {connect} from 'react-redux';
class TodoList extends Component{
render(){
return(
<div>
<input
value={this.props.inputValue}//使用store传的值
onChange={this.props.changeInputValue}
/>
<button onClick={this.props.addItem}>提交</button>
<ul>
{
this.props.list.map((item,index)=>{
return <li key={index} onClick={()=>this.props.deleteItem(index)}>{item}</li>
})
}
</ul>
</div>
)
}
}
//规定映射条件 store里的state映射到组件的props里
const mapStateToProps=(state)=>{
return {
inputValue:state.inputValue,//inputValue是指组件this.props.inputValue,state.inputValue是指store里的inputValue
list:state.list,
}
};
//把store.dispatch映射到组件的props上
const mapDispatchToProps=(dispatch)=>{
return {
//把这个函数映射到组件的props上
changeInputValue(e){
const action={//1:创建action消息
type:'change_input_value',
value:e.target.value,//把输入框里的值传给store
};
dispatch(action);//2:把这个消息传给store处理
},
addItem(){
const action={
type:'add_item',
};
dispatch(action);
},
deleteItem(index){
const action={
type:'delete_item',
index:index,
};
dispatch(action);
}
}
};
//目的:使TodoList和store做链接
export default connect(mapStateToProps,mapDispatchToProps)(TodoList);
//store/index.js
import {createStore} from 'redux';
import reducer from './reducer';
const store=createStore(reducer);
export default store;
//store/reducer.js
const defaultState={//创建一个默认state
inputValue:'',
list:[],
};
export default (state=defaultState,action)=>{
3:处理store自动传过来的action消息
if(action.type==='change_input_value'){
const newState=JSON.parse(JSON.stringify(state));//对原有数据进行深拷贝、
newState.inputValue=action.value;
return newState;//4:reducer把newState传给store,store进行更新处理
}
if (action.type==='add_item'){
const newState=JSON.parse(JSON.stringify(state));//对原有数据进行深拷贝、
newState.list.push(newState.inputValue);
newState.inputValue='';
return newState;
}
if (action.type==='delete_item'){
const newState=JSON.parse(JSON.stringify(state));//对原有数据进行深拷贝、
newState.list.splice(action.index,1);//删除从索引开始的1个
return newState;
}
return state;
}
pinia
// 定义仓库
import { defineStore } from 'pinia'
const useMainStore = defineStore('main', {
state() {
return {
// 状态变量
count: 0
}
},
getters: {
countTen(state) {
return state.count + 10
}
},
actions: {
increment() {
this.count++
}
}
})
export default useMainStore
<script setup lang="ts">
import useMainStore from './store/index.js'
const mainStore = useMainStore()
function increment() {
mainStore.increment()
}
</script>
<template>
<div>
count:{{mainStore.count}}
countTen:{{mainStore.countTen}}
</div>
<button @click="increment">
increment
</button>
</template>
用户发出 Action,Reducer 函数算出新的 State,View 重新渲染。但是,一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。 怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件. redux-thunk。
20.route Vue2:
//home.vue
<template>
<div>
<h1>home</h1>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
data () {
return {
msg: "我是home 组件"
}
}
}
</script>
//App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<header>
<!-- router-link 定义点击后导航到哪个路径下 -->
<router-link to="/home">Home</router-link>
</header>
<!-- 对应的组件内容渲染到router-view中 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
//index.js
import Vue from "vue";
import VueRouter from "vue-router";
// 引入组件
import home from "./home.vue";
import about from "./about.vue";
// 要告诉 vue 使用 vueRouter
Vue.use(VueRouter);
const routes = [
{
path:"/home",
component: home
}
]
var router = new VueRouter({
routes
})
export default router;
//main.js
import Vue from 'vue'
import App from './App.vue'
// 引入路由
import router from "./router.js" // import router 的router 一定要小写, 不要写成Router, 否则报 can't match的报错
new Vue({
el: '#app',
router, // 注入到根实例中
render: h => h(App)
})
//router.js
const routes = [
{
path:"/home",
component: home
},
{
path: "/about",
component: about
},
// 重定向
{
path: '/',
redirect: '/home'
}
]
Vue3与Vue2不同点:
//Vue2.x
//通过this获取router实例
export default{
mounted() {
this.getRouter();
},
methods: {
getRouter() {
console.log(this.$route);
console.log(this.$router);
},
},
}
//Vue3.x
//第一种 通过使用 getCurrentInstance 方法获取当前组件实例
import { getCurrentInstance } from "vue";
export default {
setup(props, context) {
const { ctx } = getCurrentInstance();
console.log(ctx.$router.currentRoute.value);
},
};
//第二种通过userRoute和userRouter
import { useRoute, useRouter } from "vue-router";
export default {
setup(props, context) {
const currRoute = useRoute();
const currRouter = useRouter();
console.log(currRoute);
console.log(currRouter);
},
};
React 和Vue差不多:
//Test.js
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import Index from '../pages/Index'
import List from './List'
class Test extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<Router>
<ul>
<li>
<Link to="/">首页</Link>
</li>
<Route path="/" exact component={Index}></Route>
</ul>
</Router>
</div>
);
}
}
export default Test;
//index.js
import React, { Component } from 'react';
class Index extends Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
return (
<div>
index
</div>
);
}
}
export default Index;
------------------------------------3.13更新 21.diff算法
- react-diff: 递增法
移动节点:移动的节点称为α,将α对应的真实的DOM节点移动到,α在新列表中的前一个VNode对应的真实DOM的后面react
添加节点:在新列表中有全新的VNode节点,在旧列表中找不到的节点须要添加(经过find这个布尔值来查找)面试
移除节点:当旧的节点不在新列表中时,咱们就将其对应的DOM节点移除(经过key来查找肯定是否删除)算法
不足:从头至尾单边比较,容易增长比较次数数组
- vue2-diff: 双端比较
DOM节点何时须要移动和如何移动,总结以下:
- 头-头:不移动
- 尾-尾:不移动
- 头-尾: 插入到旧节点的尾节点的后面
- 尾-头:插入到旧列表的第一个节点以前
- 以上4种都不存在(特殊状况):在旧节点中找,若是找到,移动找到的节点,移动到开头;没找到,直接建立一个新的节点放到最前面
添加节点【oldEndIndex以及小于了oldStartIndex】:将剩余的节点依次插入到oldStartNode的DOM以前post
移除节点【newEndIndex小于newStartIndex】:将旧列表剩余的节点删除便可学习
- vue3-diff: 最长递增子序列
在 Vue3 里 patchKeyedChildren 为
头和头比 尾和尾比 基于最长递增子序列进行移动/添加/删除 看个例子,比如
老的 children:[ a, b, c, d, e, f, g ] 新的 children:[ a, b, f, c, d, e, h, g ] 1、先进行头和头比,发现不同就结束循环,得到 [ a, b ] 2、再进行尾和尾比,发现不同就结束循环,得到 [ g ] 3、再保存没有比较过的节点 [ f, c, d, e, h ],并通过 newIndexToOldIndexMap 拿到在数组里对应的下标,生成数组 [ 5, 2, 3, 4, -1 ],-1 是老数组里没有的就说明是新增 4、然后再拿取出数组里的最长递增子序列,也就是 [ 2, 3, 4 ] 对应的节点 [ c, d, e ] 5、然后只需要把其他剩余的节点,基于 [ c, d, e ] 的位置进行移动/新增/删除就可以了
区别优化
- react和vue2的比较:
- vue2双端比较解决react单端比较致使移动次数变多的问题,react只能从头至尾遍历,增长了移动次数
- vue2和vue3的比较:都用了双端指针
- vue3和react比较:vue3在判断是否须要移动,使用了react的递增法
几个算法看下来,套路就是找到移动的节点,而后给他移动到正确的位置。把该加的新节点添加好,把该删的旧节点删了,整个算法就结束了。
组件传值
三、vue3父子值传递 1、父传子 父组件
//通过动态绑定的方式
<template>
<div>
<button>父组件</button>
<Child :sex="sex" @FatherMethod"></Child>
</div>
<template>
<script setup lang="ts">
//引入子组件
import Child from './child.vue';
import { ref } from "vue";
const sex = ref('male');
const FatherMethod = () => {
console.log('嗨嗨嗨,我是父组件');
};
</script>
子组件
<template>
<div>
<button @click="ChildMethod">子组件</button>
</div>
</template>
<script setup lang="ts">
//子组件接收
const props = defineProps(["sex"])
const ChildSex = props.sex;
//子组件中自定义事件
const emits = defineEmits(["FatherMethod"]);
const ChildMethod = () => {
emits("FatherMethod");
};
</script>
子传父
父组件
<template>
<div>
<button @click="FatherMethod">父组件</button>
<Child ref="Childs">{{sex}}</Child>
</div>
<template>
<script setup lang="ts">
//引入子组件
import Child from './child.vue';
import { ref } from "vue";
//声明子组件
const Childs: any = ref(null);
const FatherMethod = () => {
//子组件.value.方法
Childs.value.ChildMethod();
};
</script>
子组件
<template>
<div>
<button>子组件</button>
</div>
</template>
<script setup lang="ts">
//子组件中定义事件第一种方法
const emits = defineEmits(["sex"]);
//子组件中定义事件第一种方法
function height() {
//第一个参数是提交的变量名,后面是参数
emit('height', 192);
};
//子组件的方法
const ChildMethod = () => {
console.log('嗨害嗨,我是子组件');
};
//暴露出去
defineExpose({
ChildMethod
})
</script>
编译原理
4、编译优化:PatchFlag(静态标记)、hoistStatic(静态提升)与渲染复用
Vue 2.x 中的虚拟 DOM 是全量对比的模式,而到了 Vue 3.0 开始,新增了静态标记(PatchFlag)。
在更新前的节点进行对比的时候,只会去对比带有静态标记的节点。并且 PatchFlag 枚举定义了十几种类型,用以更精确的定位需要对比节点的类型。
export const enum PatchFlags {
TEXT = 1,// 动态的文本节点
CLASS = 1 << 1, // 2 动态的 class
STYLE = 1 << 2, // 4 动态的 style
PROPS = 1 << 3, // 8 动态属性,不包括类名和样式
FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较
HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点
STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 Fragment
KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512
DYNAMIC_SLOTS = 1 << 10, // 动态 solt
HOISTED = -1, // 特殊标志是负整数表示永远不会用作 diff
BAIL = -2 // 一个特殊的标志,指代差异算法
}
在vue2中,无论元素是否参与更新,在生成vdom树时都会重新创建该元素,而在vue3里有一种静态提升的概念,这个概念的核心思想是对于PatchFlag标记出的,不参与更新的元素提出来,只需创建一次,后续渲染时直接复用就行
从上面我们就可以看到 hostname 的 div 标签就是标记的 HOISTED -1 即静态标签,不会被 diff 的