目录
目录
React18
特点
声明式编码
单向数据流
组件化
虚拟DOM(Virtual Dom)(同Vue)
Diff算法(同Vue)
组件属性
props
state
refs
总结
受控组件和非受控组件
事件event
事件处理的几种方法
事件中this的处理
事件传参处理
鼠标事件 mouseenter与mouseover区别
跨组件通信
生命周期
状态提升
复用组件
Render Props模式
HOC高阶组件模式
Hooks
useState
useReducer
useEffect
useRef
倒计时(⭐手写)
useCallback和useMemo
自定义Hook
StrictMode严格模式
Router
路由模式(同Vue)
基础路由搭建
重定向路由
自定义全局守卫
动态路由
Redux状态管理库
应用
React脚手架
Angular,Vue,React对比
Angular
React和Vue
MVC、MVP、MVVM模式
MVC (Model View Controller)
MVP(Model View Presenter)
MVVM (Model View View Model)
React是用来构造用户界面的JS库
在React18中,需要使用两个文件来初始化框架:
react.development.js 或 react模块 -> 生成虚拟DOM
react-dom.development.js 或 react-dom/client模块 -> Diff算法 + 处理真实DOM
下面就是初始化React程序的代码。
Document
虚拟DOM,组件化设计模式,声明式代码,单向数据流,使用jsx描述信息
因没有指令的概念,所以条件渲染和列表渲染都要通过命令式编程来实现(即JS本身的能力)
map()方法
let app = document.querySelector('#app');
let root = ReactDOM.createRoot(app);
let data = [
{ id: 1, text: 'aaa' },
{ id: 2, text: 'bbb' },
{ id: 3, text: 'ccc' }
];
let element = (
{
data.map(v=>- {v.text}
)
}
);
root.render(element);
数据主要从父节点传到子节点(通过props),如果父级的某个props改变了,React会重新渲染所有子节点
可复用的代码可以抽成组件共同使用(UI,方法等)
每个UI块都是一个组件,每个组件都有一个状态。
虚拟 DOM,根据模板生成一个js对象(使用createElement,方法),取代真实的 DOM 。
当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM 树,将状态全部保存起来
Vue和React框架都会自动控制DOM的更新,而直接操作真实DOM是非常耗性能的,所以才有了虚拟DOM的概念
React遵循可观察的模式,并监听状态变化。当组件的状态改变时,React更新虚拟DOM树。
缺点:首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢
通过同层的树节点进行比较的高效算法,比较方式:diff整体策略为:深度优先,同层比较
总的来说就是减少DOM,重绘和回流
react生成的新虚拟DOM和旧虚拟DOM的比较规则:
如果内容没有变化,就直接只用之前旧的真实DOM
如果内容发生了变化,就生成新的真实DOM
根据数据创建新的真实的DOM,随后渲染到页面上
每个 React 组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。
对外公开属性,只读
传递数据
传递函数
// 子组件
class Head extends React.Component {
render(){
this.props.getData('子组件的问候~~~')
return (
Head Component
);
}
}
// 父组件
class Welcome extends React.Component {
getData = (data) => {
console.log(data)
}
render(){
return (
hello world, {this.props.msg}
构造函数获取props
class Foo {
constructor(props){
this.props = props;
}
}
class Bar extends Foo {
constructor(props){
super(props);
console.log(this.props);
}
render(){
console.log(this.props);
return '';
}
}
let props = {
msg: 'hello world'
};
let b = new Bar(props);
b.props = props;
b.render();
多属性传递props
class Welcome extends React.Component {
render(){
//解构
let { msg, username, age } = this.props;
console.log( isChecked );
return (
hello world, {msg}, {username}, {age}
);
}
}
let info = {
msg: 'hi react',
username: 'xiaoming',
age: 20
};
let element = (
);
设置props初始值和类型
import PropTypes from 'prop-types'
class Welcome extends React.Component {
static defaultProps = {
age: 0
}
static propTypes = {
age: PropTypes.number
}
...
}
组件的私有属性,值是对象(可以包含多个key-value的组合)
通过state的变化设置响应式视图,受控于当前组件
class Welcome extends React.Component {
state = {
msg: 'hello',
count: 0
}
handleClick = () => {
this.setState({
msg: 'hi'
});
}
render(){
console.log('render');
return (
{this.state.msg}, {this.state.count}
);
}
}
let element = (
);
React操作原生DOM跟Vue框架是类似的,都是通过ref属性来完成的
class Welcome extends React.Component {
myRef = React.createRef()
handleClick = () => {
//console.log(this.myRef.current); // 原生DOM input
this.myRef.current.focus();//获取焦点
}
render(){
return (
);
}
}
props
公开,单向数据流值,父子组件间的唯一通信,不可改
1.每个组件对象都会有props(properties的简写)属性
2.组件标签的所有属性都保存在props中
3.内部读取某个属性值:this.props.propertyName
state
私有(通过Ajax获取回来的数据,一般都是私有数据),
React 把组件看成是一个状态机(State Machines),
只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。
refs
当需要获取某一个真实的DOM元素来交互,比如文本框的聚焦、触发强制动画等
当需要操作的元素和获取的元素是同一个时,无需ref
非受控组件
现用现取,官方建议尽量少用ref,用多了有一定的效率影响
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username, password} = this//拿到的是form下的username, password结点
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
}
render() {
return (
受控组件
将输入维护到state,等需要时再从state取出来
class Login extends React.Component {
//state最好要初始化状态
state = {
username: '', //用户名
password: '' //密码
}
//保存用户名到状态中
saveUsername = (event) => {
this.setState({username: event.target.value})
}
//保存密码到状态中
savePassword = (event) => {
this.setState({password: event.target.value})
}
//表单提交的回调
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username, password} = this.state//获得的是state下的username,password值
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
React中的事件都是采用事件委托的形式,所有的事件都挂载到组件容器上,其次event对象是合成处理过的
import React from 'react'
class Test extends React.Component{
handleClick2(){
console.log('click2')
}
hangleClick4 = () =>{
console.log('click4')
}
render(){
return(
)
}
}
export default Test
class Welcome extends React.Component {
handleClick = (ev) => { //推荐 public class fields语法,箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。
console.log(this); //对象
console.log(ev);
}
handleClick(){ //不推荐 要注意修正指向
console.log(this); //按钮
}
render(){
return (
hello world
);
}
}
let element = (
);
推荐采用函数的高阶方式,具体代码如下:
class Welcome extends React.Component {
handleClick = (num) => { // 高阶函数
return (ev) => {
console.log(num);
}
}
render(){
return (
hello world
);
}
}
let element = (
);
mouseenter: 鼠标进入被绑定事件监听元素节点时触发一次,再次触发是鼠标移出被绑定元素,再次进入时。而当鼠标进入被绑定元素节点触发一次后没有移出,即使鼠标动了也不再触发。
mouseover: 鼠标进入被绑定事件监听元素节点时触发一次,如果目标元素包含子元素,鼠标移出子元素到目标元素上也会触发。
mouseenter 不支持事件冒泡 mouseover 会冒泡
Welcome传递给Title:
let MyContext = React.createContext();
class Welcome extends React.Component {
state = {
msg: 'welcome组件的数据'
}
render(){
return (
Hello Welcome
);
}
}
class Head extends React.Component {
render(){
return (
Hello Head
);
}
}
class Title extends React.Component {
static contextType = MyContext
componentDidMount = () => {
console.log( this.context );
}
render(){
return (
Hello Title { value => value }
);
}
}
let element = (
);
通过
组件携带value
属性进行向下传递的,
那么接收的语法是通过
组件。
也可以定义一个静态方法static contextType = MyContext
,这样就可以在逻辑中通过this.context
来拿到同样的值。
生命周期钩子函数就是回调函数,
挂载
更新
卸载
可以看到挂载时和更新时都有render
这个方法。这就是为什么state改变的时候,会触发render
重渲染操作。
class Welcome extends React.Component {
state = {
msg: 'hello world'
}
constructor(props){
super(props);
console.log('constructor');
}
componentDidMount = () => {
// react中发起ajax请求的初始操作,在这个钩子中完成
console.log('componentDidMount');
}
componentDidUpdate = () => {
// 等DOM更新后触发的钩子
console.log('componentDidUpdate');
}
componentWillUnmount = () => {
console.log('componentWillUnmount');
}
handleClick = () => {
/* this.setState({
msg: 'hi react'
}); */
//this.forceUpdate();
root.unmount(); // 触发卸载组件
}
render(){
console.log('render');
return (
{ this.state.msg }
);
}
}
多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。对子组件操作,子组件不改变自己的状态。
组件之间使用一个值为函数的 prop 共享代码的简单技术。
class MouseXY extends React.Component {
state = {
x: 0,
y: 0
}
componentDidMount = () => {
document.addEventListener('mousemove', this.move)
}
componentWillUnmount = () => {
document.removeEventListener('mousemove', this.move)
}
move = (ev) => {
this.setState({
x: ev.pageX,
y: ev.pageY
});
}
render(){
return (
{ this.props.render(this.state.x, this.state.y) }
);
}
}
class Welcome extends React.Component {
render(){
return (
hello world, {x}, {y}
} />
);
}
}
let element = (
);
render属性后面的值是一个回调函数,通过这个函数的形参可以得到组件中的数据,从而实现功能的复用。
参数为组件,返回值为新组件的函数
function withMouseXY(WithComponent){
return class extends React.Component {
state = {
x: 0,
y: 0
}
componentDidMount = () => {
document.addEventListener('mousemove', this.move)
}
componentWillUnmount = () => {
document.removeEventListener('mousemove', this.move)
}
move = (ev) => {
this.setState({
x: ev.pageX,
y: ev.pageY
})
}
render(){
return
}
}
}
class Welcome extends React.Component {
render(){
return (
hello world, { this.props.x }, { this.props.y }
);
}
}
const MouseWelcome = withMouseXY(Welcome)
let element = (
);
Hook 是 React 16.8 的新增特性,是一个特殊的函数,它可以在不写 class(即不用extends React.Component) 的情况下“钩入” React 特性(即组件化模块的特性)。
等同组件中的setState()
let { useState } = React;//只能在最顶层使用Hook
let Welcome = (props) => {//只能在函数组件中使用Hook
//count的初始值0,设置count的函数
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1)
}
return (
hello world, { count }
);
}
与组件中的state一样自动批处理(即合并修改,每次set只渲染一次),可用flushSync
方法消除
(flush synchronization清洗同步)
setCount((count)=> count+1)
setCount((count)=> count+1)
setCount((count)=> count+1)
{ count } // 渲染 3
let { useState } = React;
let { flushSync } = ReactDOM;
let Welcome = (props) => {
const [count, setCount] = useState(0);
const [msg, setMsg] = useState('hello');
const handleClick = () => {
flushSync(()=>{
setCount(count + 1)
})
flushSync(()=>{
setMsg('hi')
})
}
return (
hello world, { count }, { msg }
);
}
useState中的值在修改的时候,并不会进行原值的合并处理,所以使用的时候要注意。可利用扩展运算符的形式来解决合并的问题。
const [info, setInfo] = useState({
username: 'xiaoming',
age: 20
})
setInfo({
...info,
username: 'xiaoqiang'
})
如果遇到初始值需要大量运算才能获取的话,可采用惰性初始state,useState()添加回调函数的形式来实现。
const initCount = () => {
console.log('initCount');
return 2*2*2;
}
const [count, setCount] = useState(()=>{
return initCount();
});
这样初始只会计算一次,并不会每次都重新进行计算。
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。
let { useReducer } = React;
let loginState = {//体现整体关联性与统一性**
isLogin: true,
isLogout: false
}
let loginReducer = (state, action) => {
switch(action.type){
case 'login':
return { isLogin: true, isLogout: false }
case 'logout':
return { isLogin: false, isLogout: true }
default:
throw new Error()
}
}
let Welcome = (props) => {
const [ state, loginDispatch ] = useReducer(loginReducer, loginState);
const handleLogin = () => {
loginDispatch({ type: 'login' });
}
const handleLogout = () => {
loginDispatch({ type: 'logout' });
}
return (
{ state.isLogin ? : }
);
}
在函数组件中执行副作用操作,副作用即:DOM操作、获取数据、记录日志等
代替类组件中的生命周期钩子函数。
如果不传参:相当于render之后就会执行
如果传空数组:相当于componentDidMount
如果传数组:相当于componentDidUpdate
如果返回回调函数:相当于componentWillUnmount,会在组件卸载的时候执行清除操作。
effect 在每次渲染的时候都会执行。React 会在执行当前 effect 之前对上一个 effect 进行清除。
let Welcome = (props) => {
const [count, setCount] = useState(0);
//异步函数,在浏览器渲染DOM后触发的
useEffect(()=>{
// 初始 和 更新 数据的时候会触发回调函数
console.log('didMount or didUpdate');
return ()=>{ // 这里回调函数可以用来清理副作用
console.log('beforeUpdate or willUnmount');
}
})
const handleClick = () => {
//setCount(count + 1);
root.unmount();//卸载
}
return (
hello world, { count }
);
}
关注点分离后,改变一个数据后,例如count,那么msg相关的useEffect也会触发,
给useEffect设置第二个参数,只重新触发自己的useEffect回调函数,即响应式的数据
const [count, setCount] = useState(0);
useEffect(()=>{
console.log(count);
}, [count])
const [msg, setMsg] = useState('hello');
useEffect(()=>{
console.log(msg);
}, [msg])
大部分情况下我们采用useEffect(),性能更好。
但当你的useEffect里面的操作需要处理DOM,并且会改变页面的样式,
就需要useLayoutEffect,否则可能会出现闪屏问题。
let { useRef } = React;
let Welcome = (props) => {
const myRef = useRef()
...}
等同于
React.createRef()
函数转发
把ref添加到函数组件上,把ref对应的对象转发到子组件的内部元素身上。
let Head = React.forwardRef((props, ref) => {
return (
hello Head
)
})
let Welcome = (props) => {
const myRef = useRef()
const handleClick = () => {
myRef.current.focus();
}
return (
let count = useRef(0); // 与state类似,有记忆功能,可理解为全局操作
const handleClick = () => {
count.current++;
...
}
//利用setTimeOut,每秒将数值减一
//利用useRef设置定时器,方便清楚
const [time,setTime]=useState(null)//倒计时时间
const timeRef=useRef()//设置延时器
//倒计时
useEffect(()=>{
//如果设置倒计时且倒计时不为0
if(time&&time!==0)
timeRef.current=setTimeout(()=>{
setTime(time=>time-1)
},1000)
//清楚延时器
return ()=>{
clearTimeout(timeRef.current)
}
},[time])
React 使用 Object.is 比较算法 来比较 state。
当组件数据没有变化时,是不会重新渲染试图,如下,cosole.log不会执行
let Welcome = (props) => {
const [ count, setCount ] = useState(0);
const handleClick= () => {
setCount(0);
}
console.log(123);
return (
hello Welcome { Math.random() }
);
}
当变化后state与当前state相同时,包括变化前的渲染一共会渲染两次
因为React 可能仍需要在跳过渲染前 渲染该组件。
但React 不会对组件树的“深层”节点进行不必要的渲染
React.memo
避免可以没有必要的重新渲染,类似于类组件中的纯函数概念。
将{ Math.random() } 包装成函数组件
let Welcome = (props) => {
const [ count, setCount ] = useState(0);
const handleClick= () => {
setCount(1);
}
return (
hello Welcome
在渲染期间执行了高开销的计算,则可以使用 useMemo
来进行优化。
useCallback返回一个可记忆的函数,useMemo返回一个可记忆的值,useCallback只是useMemo的一种特殊形式。
let Welcome = (props) => {
const [ count, setCount ] = useState(0);
const handleClick= () => {
setCount(count+1);
}
const foo = () => {}
return (
hello Welcome
当点击按钮的时候,
组件会进行重新渲染,因为每次重新触发通过useCallback和useMemo可以不让foo函数重新生成,使用之前的函数地址
从而减少子组件的渲染,提升性能。
const foo = useCallback(() => {}, [])
const foo = useMemo(()=> ()=>{}, []) // 针对函数
const bar = useMemo(()=> [1,2,3], []) // 针对数组
const foo = useMemo(()=> ()=>{}, [count]) // 第二个参数为依赖项,当count改变时,函数重新创建
实现函数组件复用
let { useState, useEffect } = React
let useMouseXY = () => {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
useEffect(()=>{
function move(ev){
setX(ev.pageX)
setY(ev.pageY)
}
document.addEventListener('mousemove', move)
//如果返回回调函数:相当于componentWillUnmount,会在组件卸载的时候执行清除操作。
return () => {
document.removeEventListener('mousemove', move)
}
}, [])//如果传空数组:相当于componentDidMount
return {
x,
y
}
}
let Welcome = ()=>{
const {x, y} = useMouseXY()
return (
hello Welcome, {x}, {y}
)
}
StrictMode
是一个用来突出显示应用程序中潜在问题的工具。用于开发环境
与 Fragment
一样,StrictMode
不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。例如:
发布环境下关闭严格模式,以避免性能损失。
路由是根据不同的url地址展示不同的内容或页面,是SPA(单页应用)的路径管理器,
1.一个路由就是一个映射关系(key:value)
2.key为路径, value可能是function或component
Router 用于定义多个路由,当用户定义特定的 URL 时,如果此 URL 与 Router 内定义的任何 “路由” 的路径匹配,则用户将重定向到该特定路由。
React中的路由模式跟Vue中一样,分为history和hash模式。默认是hash
Hash模式
hash——即地址栏URL中的#符号(此hash不是密码学里的散列运算)。比如在 http://localhost:8080/#/donate 中,hash的值就是#/donate,我们在浏览器中可以通过BOM中的window.location.hash来获取当前URL的hash值
注:BOM(Browser Object Model) 是指浏览器对象模型,BOM由多个对象组成,其中代表浏览器窗口的Window对象是BOM的顶层对象,其他对象都是该对象的子对象。
history模式
通过在host后,直接添加斜线和路径来请求后端的一种URL模式。
图中pathname变量为/donate,而hash值为空。
原理
区别
在hash模式中,我们刷新一下上面的网页,可以看到请求的URL为http://localhost:8080/,没有带上#/donate,说明hash 虽然出现 URL 中,#后面的内容是不会包含在http请求中的,对后端完全没有影 响,因此改变 hash 不会重新加载页面。
在history模式中,刷新一下网页,明显可以看到请求url为完整的url,url完整地请求了后端:
前端的 URL 必须和实际向后端发起请求的 URL 一致,否则返回 404 错误
在React中
history模式:createBrowserRouter
IE9及以下不兼容,需要由web server支持,在web client这边window.location.pathname被react router解析
hash模式:createHashRouter
不需要由web server支持,因为它的只有‘/’path需要由web server支持,而#/react/route URL不能被web server读取,在web client这边window,location.hash被react router解析
history的好处是可以进行修改历史记录,并且不会立刻像后端发起请求。不过如果对于项目没有硬性标准要求,可以直接使用hash模式开发。
import { createBrowserRouter, createHashRouter } from 'react-router-dom'
//路由表
export const routes = [];
//路由对象
const router = createBrowserRouter(routes);
export default router;
接下来让路由配置文件与React结合,需要在主入口index.js进行操作,如下:
import { RouterProvider } from 'react-router-dom'
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
路由表的配置字段如下:
path:指定路径
element:对应组件
children:嵌套路由
//路由表
export const routes = [
{
path: '/',
element: ,
children: [
{
path: '',
element:
},
{
path: 'about',
element: ,
children: [
{
path: 'foo',
element: ,
},
{
path: 'bar',
element: ,
}
]
}
]
}
];
接下来就是显示路由区域,利用
import React from "react";
import { Outlet, Link } from 'react-router-dom'
function App() {
return (
hello react
首页 | 关于
);
}
export default App;
可以看到 组件用于声明式路由切换使用。同样
import React from 'react'
import './About.scss'
import { Outlet, Link } from 'react-router-dom'
export default function About() {
return (
About
foo | bar
)
}
访问的URL跳转到另一个URL上,从而实现重定向的需求。
import { createBrowserRouter, createHashRouter, Navigate } from 'react-router-dom'
children: [
{
index: true,// 默认路由
element: ,
},
{
path: 'foo',
element:
},
{
path: 'bar',
element:
}
]
全局守卫:包裹根组件,访问根组件下面的所有子组件都要先通过守卫进行操作
在React里面,它不像Vue一样,为我们提供和很多方便的功能,一些功能都需要自己去进行封装比如说路由拦截
在/src/components/BeforeEach.jsx下创建守卫的组件。
import React from 'react'
import { Navigate } from 'react-router-dom'
import { routes } from '../../router';
export default function BeforeEach(props) {
if(true){
return
}
else{
return (
{ props.children }
)
}
}
根据判断的结果,是否进入到组件内,还是重定向到其他的组件内。
调用BeforeEach.jsx,index.js通过路由配置文件引入,如下:
export const routes = [
{
path: '/',
element: //包裹根组件APP
}
]
根据不同的URL,可以访问同一个组件。在React路由中,通过path字段来指定动态路由的写法。
foo/xxx都能访问到Foo组件,本身就有实现了动态路由
import { Outlet, Link } from 'react-router-dom'
export default function About() {
return (
foo 123 | foo 456
)
}
{
path: 'foo/:id',
element:
}
id
就是变量名,可以在组件中用useParams
来获取到对应的值。
import { useParams } from 'react-router-dom'
export default function Foo() {
const params = useParams()
return (
Foo, { params.id }
)
}
Redux就像Vue中的Vuex或Pinia是一样专门用于做状态管理的JS库(不是react插件库)。
只不过Redux比较独立,可以跟很多框架结合使用,不过主要还是跟React配合比较好,也是最常见的React状态管理的库。
redux相当于在顶层组件之上又加了一个组件
//state存储共享数据
function counterReducer(state={count: 0}, action) {//reducer修改state
switch(action.type){
case 'inc':
return {count: state.count + state.payload}
default:
return state;
}
}
const store = createStore(counterReducer)
export default store
这样store对象就可以在其他组件中进行使用了,例如在
import React from 'react'
import './Bar.scss'
import { useSelector,useDispatch } from 'react-redux'
export default function Bar() {
const count = useSelector((state)=> state.count)//获取共享状态
const dispatch=useDispatch();//修改共享状态
const handleClick = () => {
dispatch({//dispatch触发Reducer,分发action
type: 'inc',
payload: 5
})
}
return (
Bar, { count }
)
}
在主模块中进行注册。
import { RouterProvider } from 'react-router-dom'
import router from './router';
import { Provider } from 'react-redux'
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
//注册状态管理与React结合,自动完成重渲染
);
从项目的整体看
从组件角度看
开始一个react项目,不用手动配置,直接开发
框架比较成熟完整,过于庞大,上手难;
相同点 :
创建UI的js库
都是使用了虚拟dom
组件化开发
父子之间通信单项数据流
都支持服务端渲染
不同点:
vue轻量级框架,其核心库只关注视图层,简单小巧、易学易上手;
个人维护项目; 适合于移动端开发;
reacct 的jsx,可读性好
vue的是 template
数据变化,react 手动 setState vue自动响应式处理 proxy object.DefineProperty
react 单向数据流 ,vue双向数据流
react 的 redux mobx
vue 的vuex pinia
【优点】
耦合性低,方便维护,可以利于分工协作 重用性高
【缺点】
使得项目架构变得复杂,对开发人员要求高
从MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,
视图和业务逻辑分开。
View视图层,Model 数据模型,ViewModel 是它们双向绑定的桥梁,自动同步更新
【优点】
相比mvp各层的耦合度更低,一个viewmodel层可以给多个view层共用(一对多),提高代码的可重用性。
*耦合度:模块间依赖的程度。
【缺点】
因为使用了dataBinding,增加了大量的内存开销,增加了程序的编译时间,所以适合轻量级项目。
数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题
React和Vue都用了MVVM
React单向数据流:只能由数据层的变化去影响视图层的变化
Vue双向数据绑定