vue和react编程模型对比
React
的特点:能不做的,我都不做
Vue
的特点:能帮你做的,我都帮你做
vue的编程模型
一个对象,对应一个虚拟DOM,当对象的属性改变时,
属性相关
的DOM节点全部更新
Vue为了其他考量,也引入了虚拟DOM和DOM diff
react的编程模型
每次触发dom diff 都会触发一次函数调用
一个对象,对应一个虚拟DOM,
另一个对象,对应另一个虚拟DOM,
对比两个虚拟DOM,找不同(DOM diff)最后
局部更新DOM`
带着问题学React
- 两种方式引入React & ReactDOM
- React.createElement(‘div’ | 函数 | 类 )
- 类组件、函数组件如何获取外部数据props
- 类组件、函数组件如何获取内部数据state
- 类组件如何绑定事件,可以直接 fn= ()=>{}
- 函数组件如何绑定事件,函数组件内部不用this
- vue特点,react特点
引入react
cjs - CommonJS
- 是Node.js支持的模块规范
umd - 统一模块定义
- 兼容各种模块规范(含浏览器)
理论上优先使用umd,同时支持Node.js和浏览器
最新的模块规范是使用import和export关键字
新建项目
// 全局安装
yarn global add create-reacrt-app
// 初始化目录
create-react-app react-demo-1
// 进入目录
cd react-demo-1
// webpack让JS默认走babel-loader
React组件
Element V.S. Component
对比React元素和函数组件
// App1 是一个 React 元素
App1 = React.createElement('div', null, n)
// App2 是一个 React 函数组件
App2 = ()=> React.createElement('div', null, n)
// 函数app2是延迟执行的代码,会在被调用的时候执行
// 同步异步关注的是得到结果的时机,现在主要是说执行的时机
-
React元素
createElement的返回值element可以代表一个div
但element并不是真正的div(DOM对象)
所以我们一般称element为虚拟DOM对象 -
()=>React元素
返回element的函数,也可以代表一个div
这个函数可以多次执行,每次得到最新的虚拟div
React会对比两个虚拟div,找出不同,局部更新视图,找不同的DOM算法叫做 DOM Diff 算法 -
一个返回React元素的函数就是组件
React的两种组件类
和函数
组件
// 一、函数组件
function Welcome(props){
return <h1>Hello, {props.name}</h1>;
}
// 需要return 出去
•使用方法 <Welcome name="frank"/>
// 二、类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>
}
}
// 需要return 出去
•使用方法 <Welcome name="frank"/>
props(外部数据)
Props的作用
- 接受外部
数据
(1) 只能读不能写
(2) 外部数据由父组件传递 - 接受外部
函数
(1) 在恰当的时机,调用该函数
(2) 该函数一般是父组件的函数
props的一些原则规范
- 改props的值:外部属性 由外部更改,不要在外部修改props数据
- 改props的属性: 外部的数据,不应该从内部改值
- 数据应该由原处修改。
UNSAFE_componentWillReceiveProps钩子
- 当组件接受新的props时,就会触发此钩子
- 不推荐使用这个钩子
props例
// 类组件
class Son extends React.Component {
// 初始化 props,可通过this.props获得 外部数据 对象 的地址
constructor(props){
super(props)
}
render() {
return (
<div className="Son">
// 使用组件,并自定义数据名 messageForSon 通过this.props.messageForSon读取
我是儿子,我爸对我说「{this.props.messageForSon}」
// vue 通过在属性前加冒号:编写JS,react通过中括号{}编写JS
<Grandson messageForGrandson="孙贼你好" />
</div>
);
}
}
// 函数组件
const Grandson = props => {
return (
// 数据会作为第一个参数传递过来的,通过.自定义名获取数据
<div className="Grandson">
我是孙子,我爸对我说「{props.messageForGrandson}」// 通过props接收
</div>
);
};
state(内部数据)
读用 this.state
onClick=()=>{
this.setState({x: this.state.x + 1})
// 此时的x为0,而不是1 因为它不会马上改变X的值,而是执行完后再改变
console.log(x)
}
this.state.xxx.xx.x
写用 this.setState(???,fn)
this.setState(newState,fn)
this.setState((state,props)=> newState,fn)
函数组件通过 React.useState(0) 更新UI
- useState[1] 不可变数据:无法改变旧数据
- 用this.xxx读 , this.setxxx写
setState类组件注意事项
- 调用setState才会
触发UI更新
,每次UI更新就会触发DOM diff重新对比新旧数据,检测当前页面的setState数据是否改变 - setState会
异步
的触发更新UI - 因为React没有和vue监听data那样监听state
- 不要直接修改旧state this.setState(this.state) 不可变数据
- 可以
setState({n:state.n+1})
setState类组件的属性
可以单独修改属性,其他属性会沿用上一次的值
constructor() {
super();
this.state = {
n: 0,
m: 0
};
}
addN() {
this.setState({ n: this.state.n + 1 });
// 单独修改n,m 会被覆盖为 undefined 吗? // 不会
}
addM() {
this.setState({ m: this.state.m + 1 });
// 单独修改m,n 会被覆盖为 undefined 吗? // 不会
}
写的时候会 shallow merge(浅合并)
setState 会自动将新state与旧state进行一级合并
- 类组件的setState会自动合并第一层属性,但是不会合并第二层属性
- 使用object.assign或者…扩展符
- 函数组件的setX则完全不会合并属性,第一层/第二层都不会合并
- 所以用react处理数据,经常需要用…获取其他数据
setState函数组件的属性
<button onClick={()=>setState({n: state.n + 1})}/>
// m = NAN
// 单独修改n,m 会被覆盖为 undefined 吗? // 会
<button onClick={()=>setState({m: state.m + 1})}/>
// n = NAN
// 单独修改m,n 会被覆盖为 undefined 吗? // 会
// 需要将对象的值都放在里面
<button onClick={()=>setState({...state, m: state.m + 1})}/> // n = NAN
}
React.useState(0)函数组件注意事项
- 函数组件需要通过
React.useState(0)
更新UI,因为react是通过dom diff对比数据
来更新界面的,不触发DOM diff,他就不会更新UI - 跟类组件类似的地方 - 也要通过setX(新值)来更新UI
- 跟类组件不同的地方 -
没有this
生命周期
constructor()
- 是在这里初始化 state- static getDerivedStateFromProps()
shouldComponentUpdate()
- return false 阻止更新render()
- 创建虚拟DOM- getSnapshotBeforeUpdate()
componentDidMount()
- 组件已出现在页面componentDidUpdate()
- 组件已更新componentWillUnmount()
- 组件将销毁- static getDerivedStateFromError()
- componentDidCatch()
constructor的作用
用途
- 初始化props
- 初始化state,但此时不能调用setState
- 用来写 bind this
constructor(){
super()
call = this. onClick.bind(this)
}
// 新语法写法,等同于上面代码
onClick = ()=> {}
constructor(){ }
shouldComponentUpdate的作用
用途
- 返回true表示不阻止UI更新
- 返回false表示阻止UI更新
面试常问
问
shouldComponentUpdate有什么用?答
它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值,以避免不必要的更新
使用 React.PureComponent 自动对比新旧数据
自带了shouldComponentUpdate - PureComponent 会在 render 之前
对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。如果所有 key 的值全都一样
,就不会
render;如果有任何一个 key 的值不同
,就会
render。
render
用途
- 展示视图 -
return (<div></div>)
- 如果有两个以上根元素,需要用
<React.Fragment>
包起</React.Fragment>
- 可以缩写成
<></>
一些技巧
- render里面可以写if…else / ?: / map循环
- 不能直接写for循环
示例
componentDidMount()
用途
- 在元素插入页面后执行代码,这些代码依赖DOM
- 例:获取DIV高度
- 官方推荐:发起
加载数据
的AJAX请求 - 首次渲染会执行次钩子
componentDidUpdate()
如果用户退出登录 可以在此周期里请求
用途
- 在视图更新后执行代码
- 也可以发起AJAX请求,用于
更新数据
- 首次渲染时
不会
执行此钩子 - 在此处setState可能会引起无线循环,除非放在if里
- 若 shouldComponentUpdate返回
false
, 则不会触发此钩子
componentWillUnmount()
用途
- 组件被
移出页面后被销毁
时执行代码 - unmount过的组件不会再次mount
注意
- 如果在componentDidMount() 里面
监听
了window scroll,就要在componentWillUnmount()取消
监听 - 如果在componentDidMount() 里面
创建
了Timer,就要在componentWillUnmount()取消
Timer - 如果在componentDidMount() 里面
创建
了AJAX请求,就要在componentWillUnmount()取消
请求
React事件
写法
类组件
class Son extends React.Component{
// 解决方案一 这是将函数当做对象放在了cnstructor上,直接写函数是语法糖
addN = () => this.setState({n: this.state.n + 1});
// 完整写法如下
constructor(){
this.addN = ()=> this.setState({n: this.state.n + 1})
}
// 解决方案二
addN(){
this.setState({n: this.state.n + 1})
}
// 完整写法如下
addN: functioin(){
this.setState({n: this.state.n + 1})
}
所有函数的this都是参数,由调用决定,是可变的
箭头函数的this是不变的因为箭头函数不接受this
函数组件
- 函数里没有this,指向调用者的this,因为this是调用者传给函数的参数
const Grandson = () => {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
return (
<div className="Grandson">
孙子 n:{n}
// 此时的onClick是一个回调
<button onClick={() => setN(n + 1)}>n+1</button>
m:{m}
<button onClick={() => setM(m + 1)}>m+1</button>
</div>
);
};
两种方式创建Class组件
ES5创建方式
因为ES5不支持class,才会有这种方式
import React from 'react'
const A = React.createClass({
render() {
return (
<div>hi</div>
) }
})
export default A
ES6创建方式
import React from 'react';
class B extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>hi</div>
)
}
}
export default B;
// extends constructor super 强行记忆
转译成ES5
webpack+babel将ES6翻译成ES5
使用函数组件代替类组件
对,上面讲的都是class组件,下面才是推荐使用的函数组件
函数组件面临两个问题
- 函数组件没有state
- 函数组件没有生命周期
React在V16.8.0给出的解决方案是=> Hooks API
以下是两个Hooks API
- 通过useState Hook 解决 state问题
- 通过useEffect Hook 解决 生命周期问题
useEffec模拟生命周期
模拟componentDidMount
useEffect(()=>{ console.log(' 第一次渲染 ') },[])
模拟componentDidUpdate
useEffect(()=>{ console.log(' 任意属性变更 ')})
useEffect(()=>{ console.log('n变了 ')}, [n])
模拟componentWillUnmount
useEffect(()=>{
console.log(' 第一次渲染 ')
return ()=>{
console.log(' 组件销毁了 ')
}
})
其他生命周期怎么模拟
constructor
- 函数组件执行的时候 ,相当于执行了constructor
shouldComponentUpdate
使用React.memo和useMemo可以解决
render
函数组件的返回值就是render的返回值
useUpdate
- 第一次由undefined变成0时不执行函数
- 之后发生变化时,执行函数
// 封装自定义hook
// 接收两个参数,并执行
const useUpdate = (fn,ref) =>{
const [count,setCount] = useState(0)
useEffect(()=>{
setCount(x=>x+1)
},ref)
// 控制第一次渲染页面时不执行 setCount
useEffect(()=>{
if(count > 1){
fn()
}
}, [count,fn])
}
// html
const App = (props) => {
//把f推给react监听
const [f,setf] = useState(0)
const hit = ()=>{
setF(f+1)
}
// 调用自定义hook传两个参数,第一个传fn,第二个传f
useUpdate(()=>{
console.log('变了')
},f)
return (<div>{n}<button onClick={hit}>+1</button></div>)
}
这就是自定义Hook
———————————————————————————————————————————————————————————————————
JSX - 通过babel-loader转译成JS
- babel-loader里集成了jsx-loader
- babel-loader被webpack内置了
- vue-loader没有被webpack内置
JSX - JS扩展版
X表示扩展
JSX的使用:
- 将XML转译为React.createElement
- 使用{}插入JS代码
- create-reate-app默认将JS当做JSX处理
Vue和React的区别
//Vue的vue-loader
vue封装好了指令
.vue文件里写 template标签 script标签 style标签 ,通过vue-loader变成一个构造选项
// React的JSX
// 把
<button onClick="add">+1</button> //变成
React.createElement('button', {onClick....}, '+1')
// react里需要写原生JS
// React有JSX
// 实际上jsx-loader被babel-loader取代了,而babel-loader被webpack内置了
使用JSX的注意事项
// 注意className
使用className指定作用域
return (
<div className="作用域名"></div>
)
{}
// 标签里面的JS代码都要用{}包起来
// 对象则 {{name:'frank'}} 外面的括号是JSX语法
// return 的内容要用()包起来
return (
<div>App组件</div>
)
// html
Vue的HTML写在<template>里
React把HTML混在JS/JSX文件里
JSX的条件判断
// 可以这样写
const Component = () => {
const content = (
<div>
{ n%2===0 ? <div>偶数n </div> : <span>奇数n</span> }
</div>
)
return content
}
// 还可以这样写
const Component = () => {
const inner = n%2===0 ? <div>偶数n </div> : <span>奇数n </span>
const content = (
<div>
{ inner }
</div>
)
return content
}