1、安装
React
npn i react react-dom
Vue
npm install vue
2、脚手架
React
// 1、安装脚手架
npm install create-react-app
// 2、创建demo项目
npm create-react-app myApp
Vue
// 1、安装脚手架
npn install -g @vue/cli
// 2、创建demo项目
vue create myApp
3、实例
React
import React from "React";
import ReactDom from "React-dom/client"; //将虚拟dom渲染到文档中变成真实dom
import App from "./App.tsx";
const Root = ReactDom.createRoot(
document.querySelector("#root") as HTMLElement // tsx element 需要写类型
);
Root.render(
<React.StrictMode>
{""}
// 严格模式只在开发环境生效 不会渲染可见UI
<App />
</React.StrictMode>
);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
tips:
StrictMode 目前有助于:
- 识别不安全的生命周期
- 关于使用过时字符串 ref API 的警告
- 关于使用废弃的 findDOMNode 方法的警告
- 检测意外的副作用
- 检测过时的 context API
- 确保可复用的状态
Vue
import Vue from "Vue";
import App from "./App.js"
new Vue({
el: "#app",
render: (h) => h(App)
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
4、常见的UI库搭档
场景 | 前端库 | UI库 |
---|---|---|
PC | React | Ant-Design |
PC | Vue | Element-UI、Iview |
Mobile | React | Material-UI、Antd-Mobile |
Mobile | Vue | Vant |
Mobile | Vue | Vant |
多端 | React | Taro |
多端 | Vue | uni-app、mpvue |
5、生命周期
React
分为三个阶段:挂载阶段、更新阶段、销毁阶段
常见的生命周期钩子函数:
挂载阶段:
render 适用于 class 组件(检查 props 和 state)
constructor 适用于 class 组件 (组件挂载之前会调用他的构造函数,初始化 state 在这里)
componentDidMount (初始页面依赖的数据 ajax 请求放在这里)
更新阶段:
shouldComponentUpdate
componentDidUpate
卸载阶段:
componentWillUnmount (在这里取消时间监听)
tips:
函数组件中,提供了useEffect副作用钩子,让开发者只用关注状态,React负责同步到DOM。
- useEffect 相当于 componentDidMount、componentDidUpdate、componentWillUnMount三个生命周期钩子的合并方法;
- 模拟componentDidMountuseEffech(fn,[])的第二个参数传入空数组,相当于componentDidMount
- 模拟componentDidUpdateuseEffect(fn,[dep])的第二个参数数组中传入依赖参数,相当于componentDidUpdate
- 模拟 componentWillUnMountuseEffect(()=>{return callback},[])的第一个参数函数返回的callback函数会在组件卸载前被调用,相当于componentWillUnMount
Vue
分为四个阶段:创建、挂载、更新、卸载
常见的生命周期钩子函数:
创建阶段:
beforeCreate
created
挂载阶段:
beforeMount
Mounted(初始页面依赖的数据ajax请求放在这里)
更新阶段:
beforeUpdate
updated
销毁阶段:
beforeDestroy(在这里取消时间监听)
destroied
6、模板语法
React
jsx语法
1.文本
const Demo = () => {
rerurn <div>{message}</div>
}
2.html-innerHTML
使用富文本编辑器生产的数据,回到页面时也要按html来展示,React中没有像Vue一样的v-html指令,但提供了dangerouslySetInnerHTML 属性
function MyComponent(){
const html = "<div>innerHTML</div>"
return <div dangerouslySetInnerHTML={{ _html: html }} />
}
注意这里的用法比较特殊
3.style
style 接收一个对象,width 和 height 的单位px可以省略
style 多用于添加动态样式 不推荐直接将 css 属性写在里面 可读性差
style 中的 key 使用小驼峰命名法
const Demo = () => {
return <div style={{ width: 100, fontSize:"16px" }}></div>
}
4.属性
const Demo = () => {
return <div loading={loading}></div>
}
5.js 表达式
jsx 中可以书写任意表达式,甚至可以使用map:
const Demo = () => {
let flag = true;
return <div>{ flag ? message : "暂无数据" }</div>
}
Vue
基于HTML的模板语法
1.文本
Mustache 双大括号语法
数据绑定放在 双大括号 里面
<template>
<div>{{ message }}</div>
</template>
2.html
<template>
<div v-html="html"></div>
</template>
<script>
export default {
data(){
return {
html: `<div>v-html directive</div>`
}
}
}
</script>
3.style
<template>
<div style="width:100px">{{message}}</div>
<div :style="{width:100px}"></div>
</template>
4.属性
v-bind 可缩写为冒号“:”
<template>
<div v-bind:loading="loading"></div>
</template>
- js表达式
Vue的模板语法中,只能包含单个表达式:
<template>
<div>{{ flag ? message : "暂无数据" }}</div>
</template>
<script>
export default {
data(){
return {
flag: false,
}
}
}
</script>
6.动态参数
<template>
<div v-bind[attributeName]="url">动态参数</div>
</template>
7.修饰符
- .stop 阻止冒泡 相当于 event.stopPropagation
- .prevent 阻止默认 相当于event.preventDefault()
- .lazy 数据绑定放在change时间之后
- .number 自动装换用户输入为number 类型
- .trm 自动去除用户输入的头尾空白字符
- .native 将原生事件绑定到组件
- .sync 对一个prop进行“双向绑定”
tips:
Vue 也支持jsx语法,这里仅用模板语法做对比
Vue 中 template 标签内只能有一个根节点,每个组件必须只有一个根元素
7、计算属性
计算属性的常见使用场景是:
1.对复杂逻辑的抽象
2.缓存计算结果
React 中使用 useMemo Hook 实现 Vue 中的 computed 实现的功能
import { useMemo, useState } from "react";
import { Input } from "antd";
const demo = () => {
const [ firstName, setFirstName ] = useState("");
const [ secondName, setSecodName ] = useState("");
const name = useMemo(() => {
return firstName + "" + secondName
}, [ firstName, secondName ])
const handleFisrtNameChange = ( e: any ) => {
setFirstName( e.target.value )
}
const handleSecondNameChange = ( e: any ) => {
setSecondName( e.target.value )
}
return (
<div>
<div>hello {name}</div>
<Input
placeholder="first name"
onChange={handleFisrtNameChange}
></Input>
<Input
placeholder="second name"
onChange={handleSecondNameChange}
></Input>
</div>
)
}
tips:
useMemo 可以缓存计算结果或者缓存组件
Vue
Vue 提供 computed 实现计算属性
<template>
<div>
<!-- 模版当中应该是简单的声明式逻辑 复杂逻辑我们使用 computed -->
<div>hello {{ name }}</div>
</div>
</template>
<script>
export default{
data(){
return {
firstName: "firstName",
secondName: "secondName"
}
}
computed:{
name(){
return this.fristName + "" + this.secondName
}
}
}
</script>
平时开发的时候很多同学的用法是:
1.兼容边界值
return data || [];
2.封装一个长的调用
return res.data.pageData.total;
感觉上面这两种用法都是不准确的,我想 computed 这个方法一方面其实应该是用来补充 Vue 模板语法的缺陷,在Vue的模板语法中只支持单个表达式,computed 封装一个函数正好解决了这个问题(排序或者过滤也是不错的使用场景);另一方面应该是考虑缓存的必要性,当在模板中多个地方使用的时候,应该缓存起来避免重复技算。
你可能会说函数也有抽象复杂逻辑的功能,为什么还要使用 computed,他们的不同点在于缓存结果。
computed 只有在依赖更新时才会重新计算。
8、侦听器
React
React 中只能使用 useEffect 自己封装一个侦听器,useEffect 本身其实就是一个侦听器
function useWatch( target,callback ){
const oldValue = useRef(); // useRef 用来保存旧的值
useEffect(() => {
callback( target, oldValue.current );
oldValue.current = target;
},[target])
}
Vue
Vue中监听器一般用作监听 props 的变化、route的变化、state中的数据变化,当监听的数据变化的时候执行相应的依赖逻辑:
<script>
export default{
props:["flag"]
data(){
return {};
},
watch:{
flag(newVal,oldVal){
// 监听到父组件传来的 flag 改变时 执行 initData 函数
this.initData();
}
},
methods:{
initDate(){
// do something
}
}
}
</script>
9、class
界面开发时常需要用到 class 属性来修改样式
React
React 绑定 class 使用 className 属性:
const Demo = () => {
return (
<div className="box">
<i className={isCollapse ? 'unfold' : 'fold'}></i>
</div>
)
}
Vue
Vue使用 :class 属性
<template>
<div class="box">
<i :class="[isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold']"></i>
</div>
</template>
10、条件渲染
React
React 中直接书写 js 实现条件渲染:
const Demo = () => {
if(hasToken){
return (
<div>
<div style={hasToken ? {display: "block"} : {display: "none"} }></div>
登录成功
</div>
)
}else{
return <div>未登录</div>
}
}
Vue
Vue 提供相关指令实现条件渲染
<template>
<div>
<div v-if="hasToken">
<div v-show="hasToken">hello</div>
登陆成功
</div>
<div v-else>未登录</div>
</div>
</template>
tips:
1.v-if 和 v-show 的区别
v-if直接控制是否渲染;v-show 控制 display 属性,常用于频繁切换展示的情况,不同业务考虑性能以决定使用适合的指令
2.v-for 和 v-if 的优先级
v-for 的优先级高于 v-if,应当使用 v-if 嵌套v-for
11、列表渲染
出于性能考虑,Vue 和 React 在列表渲染时都需要为子组件提供 key
React
React中使用数组的map方法渲染列表:
const Demo = () => {
const list = [1,2,3].map((item) => {
return <div key={item.toString()}>{item}</div>
})
return <div>{list}</div>
}
tips:
一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串
Vue
Vue 当中使用 v-for渲染列表
<template>
<div>
<div v-for="item in list" :key="item">{{ item }}</div>
</div>
</template>
<script>
export default {
data(){
reutrn {
list: [1,2,3]
}
}
}
</script>
12、事件处理
React
React 中使用类似原生的写法,不过是采用驼峰命名:
import { Button } from "antd";
const Demo = () => {
const handleClick = () => {
console.log("click")
};
return (
<div>
<Button onClick={handleClick}></Button>
</div>
)
}
tips:
React 中阻止默认只能显示的执行:e.preventDefault
Vue
Vue 中使用v-on指令绑定事件,v-on可以缩写为 “@”
<template>
<div>
<el-button v-on:click="handleClick"><el-button>
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
handleClick(){
console.log('click')
}
}
}
</script>
tips:
Vue 中阻止默认可以在事件中处理也是可以使用修饰符:.prevent(阻止默认)、.stop(阻止冒泡)
13、获取DOM
虽然Vue和React已经封装了虚拟dom,但是在某种场景下,我们还是需要操作DOM元素
React
React 提供了操作DOM的方法:在class组件中使用creatRef,在函数组件使用useRef Hook
1.React.createRef
将引用自动通过组件传递到子组件,常用于可复用的组件库中
class MyComponet extends React.Component {
construcotr(props){
super(props);
this.inputRef = React.createRef();
}
render(){
return <input type="text" ref={ this.inputRef } />
}
compoentDidMount(){
this.inputRef.current.focus()
}
}
2.useRef
useRef 返回一个可变的ref对象,其 .current 属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内持续存在。
function TextInputWithFocusButton() {
const inputEl = useRef < HTMLInputElement > null;
const onButtonClick = () => {
// current 指定已挂载到 DOM 上的文件输入元素
if( inputEl.current ){
inputEl.current.focus();
}
};
return (
<div>
<input ref={ inputEl } type="text" />
<button onClick={ onButtonClick } >Focus the input</button>
</div>
)
}
Vue
Vue 中提供 $ref 方法访问 dom,我们只需要跟 Node 添加 ref 属性即可:
<template>
<div>
<el-table ref="userTable"></el-table>
</div>
</template>
<script>
export default {
data() {
return {};
},
mounted() {
const userTable = this.$refs.userTable.$el; // ??? $el是怎么来的???
},
};
</script>
14、组件
注意:不管在Vue还是React中,自定义组件名都需要大写
React会将小写字母开头的组件视为原生的DOM标签Vue中的组件的name 可以使用短横线分割法或者首字母大写命名
React
1.函数组件
import { useState } from “react”
const Demo = ( props ) => {
const [ data, setData ] = useState([]); // 函数组件中使用 useState 初始化 state
useEffect(() => {}, []); // 函数组件中使用 useEffct 模拟部分生命周期钩子函数
return <div>Demo</div>
}
2.class 组件
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {}
}
}
// 生命周期钩子函数
componentDidMount(){
console.log(' did mount ')
}
render() {
return <div>Demo</div>
}
}
3.全局组件
由于全局组件很难推理,React中没有实现全局组件,需要时 import 即可
4.局部组件
函数组件和class组件文件都可以很方便的import以创建局部组件
Vue
1.全局注册组件
Vue.use 或者 Vue.component
<seript>
import Vue from 'Vue';
Vue.component("Demo", {
data() {},
template: "<div>Demo compoment</div>"
});
new Vue({
// do something
})
</seript>
import Vue from "Vue";
new Vue({
components: {
Demo: {
data(){},
template: "<div>template</div>"
}
}
})
2.局部注册组件
import.Vue 文件
import Demo from './Demo.vue'
export default{
components:{
Demo
}
}
15、组件间传参数
React
1.父子组件传递状态
使用props:
import { useState } from "react"
import { Button } from "antd"
// 父组件
const Parent = () => {
const [ msg, setMessage ] = useState("")
const changeMsg = () => {
setMessage("message")
};
return (
<div>
<Child msg={msg} changeMsg={changMsg}></Child>
</div>
)
}
// 子组件
const Child = (props) => {
const handleClick = () => {
this.props.ChangeMsg();
}
return (
<div>
<div>{props.msg}</div>
<Button onClick={ handleClick }></Button>
</div>
)
}
2.跨级组件传递状态
使用 context
// 父组件
const Context = React.createContext(); // 测试这里的入参是不是必须的
const Parent = () => {
return (
<div>
<Context.Provider
value={{ name:"ya", age: 18 }}
></Context.Provider>
</div>
)
}
// 子组件
const Child = () => {
const value = useContext(Context);
return <div>This is {value.name}</div>
}
3.EventBus
React 借助 event 库实现事假总线
不推荐使用
Vue
1.父子组件传参数
使用props
// 父组件
<template>
<Demo :msg="msg" @confirm="headleConfirm></Demo>
</template>
<script>
export default {
data(){
return {
msg: [{ id:1,content:"a" }, { id: 2 }, content: "b" ]
}
},
methods:{
headleConfirm(param){
console.log("子传父的数据", param)
}
}
}
</script>
// 子组件
<template>
<div>
<ul>
<li v-for="item in msg" :key="item.id">{{ item.content }}</li>
</ul>
<el-button @click="clickBtn"></el-button>
</div>
</template>
<script>
export default {
name: "DEMO",
props: ["msg"],
data(){
return {}
},
methods:{
clickBtn{
this.$emit("confirm",{ params:{} })
}
}
}
</script>
2.跨级组件传递参数
使用 Provide Inject 选项
父组件中:
<template>
<Demo :msg="msg"></Demo>
</template>
<script>
export default {
provide(){
return {
hadleClick: () => {
// do something
}
}
}
}
</script>
子组件中:
<template>
<div>
<el-button @click="clickBtn"></el-button>
</div>
</template>
<script>
export default {
name: "DEMO",
inject: ["clickBtn"]
data(){
return {}
},
}
</script>
3.EventBus
不推荐使用
16、插槽
插槽的作用是扩展组件
React
React 中没有插槽的概念,要实现插槽使用 props.children
// 父组件
const Parent = () => {
return (
<div>
<div>这个是父组件</div>
<Child>
<div>这个是父组件传给子组件的组件</div>
</Child>
<div>
)
}
// 子组件
const Child = ( props ) => {
return (
<div>
<div>我是子组件-header</div>
<div>{propos.children}</div>
<div>我是子组件-footer</div>
</div>
)
}
Vue
Vue 中插槽使用slot标签
1.匿名插槽(单个插槽)
父组件将一些组件传入到子组件当中
父组件
<template>
<div>
<ChildDemo>
<div>插入的内容</div>
</ChildDemo>
</div>
</template>
<script>
import ChildDemo from "./components/ChildDemo.vue"
export default {
name: 'App',
components:{
ChildDemo
},
}
</script>
子组件
<template>
<div>
<slot>
<p>默认内容</p>
</slot>
</div>
</template>
<script>
import ChildDemo from "./components/ChildDemo.vue"
export default {
name: 'ChildDemo',
data(){
return {
data: [ ],
}
}
}
</script>
2.具名插槽(多个插槽)
具名插槽可以从父组件将node 插入到指定位置,同事插入多个节点
父组件
<template>
<div id="app">
<child-demo>
<template v-slot:demoA>
<div>aaaaa</div>
</template>
<template v-slot:demoB>
<div>bbbbb</div>
</template>
</child-demo>
</div>
</template>
<script>
import ChildDemo from "./components/ChildDemo.vue";
export default{
components: {
ChildDemo
}
}
</script>
子组件
<template>
<div>
<slot name="demoA">
<p>默认内容AAA</p>
</slot>
<slot name="demoB">
<p>默认内容BBB</p>
</slot>
</div>
</template>
<script>
export default {
name: "ChildDemo"
data() {
return {
data: [ ]
}
}
}
</script>
如果使用 child 组件的时候,只会传入了 demoA,那 demoB 就会渲染默认内容
3.作用域插槽
作用域插槽时用来传递数据的插槽。子组件将数据传递到父组件将数据作为 v-slot 的一个特性绑定在子组件上传递给父组件,数据在子组件中更新的时候,父组件接收到的数据也会一同更新。
父组件:
<template>
<div id="app">
<child-demo>
<template v-slot:demoA>
<div>aaaaa</div>
</template>
<template v-slot:demoB="{ dataB }">
<div>bbbbb {{ dataB }}</div>
<ul v-for=" item in dataB " :key="item">
<li>{{ item }}</li>
</ul>
</template>
</child-demo>
</div>
</template>
<script>
import ChildDemo from "./components/ChildDemo.vue";
export default {
components: {
ChildDemo,
}
}
</script>
子组件:
<template>
<div>
<slot name="demoA">
<p>默认内容AAA</p>
</slot>
<slot name="demoB" :dataB="data">
<p>默认内容BBB</p>
</slot>
<button @click="handleClick">add</button>
</div>
</template>
<script>
let count = 4;
export default {
name: "ChlidDemo",
data() {
return {
data: [1,2,3],
}
},
methods:{
handleClick() {
this.data.push(count++)
console.log(this.data)
}
}
}
</script>
16、状态管理
React
React 的状态管理工具比较多,常用的以下三种:Redux、Mobx、Dva、Reduxjs/toolkit 这里使用 reduxjs/toolkit 为例
安装:
npm i react-redux
npm i @reduxjs/toolkit
store:
import { configureStore } from "@reduxjs/toolkit"
import {
TyedUseSelectorHook,
useDispatch,
useSelector,
} from "react-redux";
import counterSlice from "./features/counterslice";
// 使用 redux-persist
import storage from "redux-persist/lib/storage";
import { combinReducers } from "redux";
import { persistReducer } from "redux-persist"; // 持久化存储
import thunk from "redux-thunk";
const reducers = combineReducers({
counter: counterSlice,
})
const persisConfig = {
key: "root",
storage,
}
const persistedReducer = persistReducer(persistConfig, reducers);
// 创建一个redux数据
const store = configureStore({
reducer: persistedReducer,
devTools: true, // 注意 调试没事不能在生成环境使用
middleware: [thunk]
})
export default store;
slice:
import { createSlice } from "@reduxjs/toolkit";
export const counterSlice = createSlice({
name: "counter",
initialState: {
count: 1,
title: "redux toolkit pre",
},
reducers: {
increment(state,{ payload }){
state.count = state.count + payload.step;
},
decrement(state){
state.count -= 1;
}
}
})
// 导出 actions
export const { increment, decrement } = counterSlice.actions;
// 内置了thunk插件可以直接处理异步请求
export count asyncIncrement = ( payload: any ) => ( dispath: any ) => {
setTimeout(dispath(increment(payload)),2000)
}
export default counterSlice.reducer;
访问store中的数据
import { useSelector } form "react-redux";
const { counter } = useSelector(state => state.counter);
更新store中的数据
import { useDispatch } from "reactRedux"
import increment from "./slice.js"
const dispath = useDispath();
dispath(increment({ step: newStep }))
Vue
Vue2的状态管理工具 Vuex,Vue3 适用的状态管理工具Pinia (Pinia 也可以在 vue2 中使用)
这里介绍Vuex的使用:
1.state 用于保存存储在store 中的数据
2.getters 用于获取 store 中的数据
3.mutation 用于修改更新 store 中的数据 只能执行同步操作
4.actions Action函数只接受一个和state相同方法和属性的context对象 可以执行异步操作
5.modules 将store 分割成为多个模块避免一个对象过于臃肿
引入vuex
import { createStore } from "vuex";
import Vue from "vue";
const store = createStore({
state:{
count: 0,
todos: [
{ id: 1,done: true },
{ id: 2,done: false }
]
},
getters:{
donetodos( state ){
return state.filter((todo) => todo.done)
}
},
mutations: {
increment( state ){
state.count++;
}
},
actions:{
increment( context ){
context.commit("increment")
}
}
})
Vue.use( store );
在组件中访问 store:
console.log(this.$store)
在组件中访问 getters:
const doneTodos = this.$store.getters.doneTodos()
在组件中提交 mutations:
this.$store.commit("increament");
在组件中提交 actions:
Action 通过 store.dispatch 方法触发
this.$store.dispatch("increament")
多个 modules:
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
action: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
action: { ... },
}
const store = createStore({
modules:{
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
tips:
由于刷新后store中的数据会重新初始化,所以我们可以需要持久化存储;vuex搭配使用vuex-persistedstate,redux 搭配使用redux-persist。当然我们也可以结合原生的 Storage来说实现。
18、逻辑复用
React
class组件中使用mixin(已被弃用,使用方式类似于 vue 中的 mixin)、render props(渲染属性)、高阶组件(HOC)
HOC:
高阶组件是一个函数,入参是一个待包装的组件,返回值是一个包装后的组件。
HOC的封装:
import React, { Component } from "react"
export default ( WrappedComponent ) => {
return class extends Component {
constructor(props){
super(props)
this.state = { count: 0 }
}
componentDidMount(){}
const setCount = ( newCount ) => {
this.setState({count: newCount})
}
render(){
return (
<div>
<WrappedComponent state={this.state} setCount={this.getCount} {...this.props} />
</div>
)
}
}
}
HOC的使用:
import HOC from "./HOC.jsx";
import { Button } from "antd";
const SourceComponet = ( props ) => {
return (
<div>
<div>count: {props.state.count}</div>
<Button onClick={() => props.setCount(props.state.count + 1)}></Button>
</div>
)
}
const Demo = HOC(SourceCoponent);
我们可以写多个Demo组件,其中的count都是独立的互不影响
tips:
高阶组件的可读性比较差、嵌套深
render props:
render props是指一种在组件间使用一个值为函数的prop共享代码的简单技术(解决横切关注点问题)
将子组件当作 prop 传入,子组件可以获取到参数中的数据。
// Cat组件 展示一只猫咪图片 图片位置随着传入的prop值变化
class Cat extends React.Component{
render(){
const mouse = this.props.mouse;
return (
<img
src="/cat.jpg"
style={{ position: "absolute", left: mouse.x, top: mouse.y }}
/>
)
}
}
// Mouse组件 监听鼠标位置变化 将鼠标位置数据传给 render prop
class Mouse extends React.Component{
constructor( props ){
super(props);
this.handleMouseMove = this.handleMouseMove.bind( this )
this.state = { x:0, y:0 }
}
handleMouseMove(event){
this.setState({
x: event.clientX,
y: event.clientY
})
}
render(){
return (
<div stye={{ height: "100vh" }} onMouseMove={this.handleMouseMove}>
{/*
使用 render prop 动态决定要渲染的内容,而不是给一个 <Mouse> 渲染结果的静态表示
*/}
{this.props.render(this.state)}
</div>
)
}
}
// MouseTracker 组件 包含 Mouse组件,将Cat组件当作prop 传入 Mounse组件
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>鼠标移动!</h1>
<Mouse render={ (mouse) => <Cat mouse={ mouse } /> }>
</div>
)
}
}
此例中我们复用的是Mouse组件中的逻辑
tips:
1.render prop 只是一种模式,不是一定要使用名为 render 的 prop 来实现这种模式
2.日志功能是一个典型的横切关注点(横切代码中做个模块的关注点(系统的部分功能))案例
Hooks
1.函数组件中使用Hook
使用自定义hook实现真正的逻辑复用:
定义一个通用的 useRequest hook:
import { useEffect, useState } from "react";
import { Get } from "../api/server";
function useRequest(url:string){
const [ data, setData ] = useState({});
const [ err, setErr ] = useState({});
const [ loading, setLoading ] = useState(false);
useEffect(() => {
const requestDate = async () => {
setLoading(true);
const res: {[ key: string: any ]} = await Get(url);
console.log("resRequest:", res)
if( res[1].code === "00000" ){
setData(res[1].data)
}else{
setErr(res[1].description)
}
setLoading(false)
}
requestData()
},[]);
return [data, loading, err]
}
export default useRequest;
使用:
import useRequest from "./hooks/useRequset";
const Demo = () => {
const [ data, loading ] = useRequst("/admin-service/api/v1/dictionaries")
const [ data2, loading2 ] = useRequst("/admin-service/api/v1/factory_menu")
return (
<div>
<Table loading={loading}></Table>
<Table loading={loading2}></Table>
</div>
)
}
tips:
1.自定义hook使用use开头
2.自定义hook的经典案例就是 request 方法的封装
Vue
Vue 中使用 mixin 实现逻辑复用:
声明:
export default{
methods:{
async getUserInfo(){
// get info
}
}
}
使用:
<script>
import userinfoMixin from './userinfoMixin';
export default {
mixins: ['userinfoMixin']
data(){
return {}
},
mounted(){
const userinfo = await this.getUserInfo()
}
}
</script>
18、css 文件引入
React
import "@/static/css/demo.less"
Vue
<setyle>
@import url("./static/css.demo.less")
</setyle>
@import "./static/css.demo.less"
20、图片导入
React
通过 import 的方式引入:
import avatar from "@/static/img/avatar.png";
import { Avater } from "antd"
const Demo = () => {
reurn (
<div>
<Avatar src={ avatar }></Avatar>
</div>
)
}
Vue
通过 import 的方式引入:
<template>
<div>
<img :src="avatar" />
</div>
</template>
<script>
import avatar from "@/static/img/avatar.png";
export default {
data(){
return {
avatar: avatar
}
}
}
</script>
21、scoped css
scoped css 是指只作用于当前组件的CSS。局部作用域的CSS在开发中非常重要,要搞清楚css的作用域,可以有效避免样式混乱、相互覆盖。
React
1.行内样式
可读性差不推荐
const Demo = () => {
return (
<div style={{ width: 100, height: 100 }}>
<Button style={{ color: "red" }}></Button>
</div>
);
};
2.styled-components
styled-components 是第三方库,可以实现 scoped css:
import styled from "styled-components";
export const Demo = styled.div`
width: 100px;
height: 100px;
button: {
colof: red;
}
`;
Vue
Vue 在Style标签中支持 scoped 属性,以实现局部作用域的css:
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: #000000;
}
</style>
style 标签中的css只对当前组件的元素起作用,不会污染到外部组件
22、不会渲染到真实DOM的标签
为了性能考虑通常我们不希望渲染太多的DOM节点,不会渲染到真实DOM的标签帮助我们将子列表分组,而无需向Dom中添加额外的节点:
React:
const Demo = () => {
return (
<div>
<React.Fragment>
<Child1></Child1>
<Child2></Child2>
<Child3></Child3>
<React.Fragment>
</div>
)
}
Vue:
<template>
<div>node</div>
</template>
template 标签上可以使用插槽;也可以用于循环
23、router
React
React中我们使用react-router、react-router-dom 实现
在 index.tsx 中引入 BrowserRouter:
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
const root = ReatDOM.createRoot(
document.querySelector("#root") as HTMLElement
)
root.render(
<BrowserRouter>
<App />
<BrowserRouter>
)
在App.jsx 文件中声明可用的 routes:
import { Routes, Route } from "react-router-dom";
import M1 from "./M1";
import M2 from "./M2";
import M3 from "./M3";
const App = () => {
return (
<div>
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/a" element={<M1 />}></Route>
<Route path="/b" element={<M2 />}></Route>
<Route path="/c" element={<M3 />}></Route>
</Routes>
</div>
)
}
使用 menu 组件 实现点击menu切换路由:
import { useNavigation,useLocation } from "react-router-dom";
// 路由跳转方法
const navigate = useNavigate();
// 当前 path 用于绑定到 Menu 组件的 defaultOenKey
const { pathname } = useLocation();
// 点击 menuItem
const clickItem = ( param: ItemObj ) => {
navigate(param.key)
}
tips: react-router-dom 是对 react-router 的扩展,新增了DOM操作的API,提供了更多好用的API
Vue
Vue 中我们使用 vue-router实现
在index.js 中引入
import VueRouter from "vue-router";
let routes = [
{
path: "/home",
name: "",
}
]
let Routes = new VueRouter({
mode: "history",
routes: routes,
})
Vue.use(Routes); // use 方法的作用全局注入插件
new Vue({
router: routes,
})
实现路由跳转:
this.$router.push({path: "/a"});
24、组件状态缓存 keep-alive
React
React 官方没有提供 keep-alive 的组件,不过提供了 Portal Api 我们可以自己封装,这里使用第三方的库 [react-activation] 来实现
import React, { useState } from "react";
import { Input } from "antd";
import KeepAlive from "react-activation";
const Keep = () => {
const [ inputValue, setInputValue ] = useState("")
const changeInput = (e) => {
setInputValue(e.target.value)
};
return (
<div>
<div>{inputValue}/div>
<Input
placeholder="keepalive this value"
value={inputValue}
onChange={changeInput}
></Input>
</div>
)
}
const KP = () => {
return (
<div>
<KeepAlive>
<Keep></Keep>
</KeepAlive>
</div>
)
}
export default KP;
AliveScope 放在应用入口处,一般放在 Router 和 Provider 内部
import { AliveScope } from "react-activation";
const root = ReactDOM.creatRoot(
document.querySelector("#root") as HTMLElement
);
root.render(
<AliveScope>
<App />
</AliveScope>
)
Vue
Vue 提供 keep-alive 组件实现组件状态缓存
<template>
<div>
<keep-alive :max="10" :include="[]" >
<router-view>
</keep-alive>
</div>
</template>