前言
在上篇文章中已经将Vue和React中的背景、核心思想、组织形式、数据管理(props、data VS state)、组件数据交互、class与style、生命周期、事件处理进行了对比与总结,那么这篇文章主要是对比总结Vue中与react中的条件渲染(v-if VS &&)、是否显示(v-show VS style+class)、列表渲染(v-for vs map)、计算属性(computed vs useMemo+useCallback)、watch vs render、ref、表单(v-model vs value)、插槽(solt vs render prpops+this.props.children)
一、条件渲染
条件渲染用于根据条件判断是否渲染一块内容
Vue
vue中用v-if指令条件地渲染一块内容,只有当表达式返回真值时才被渲染,v-if还可以喝v-else-if、v-else配合使用,用于不同条件下的渲染,类似于js里面的if else语法。
1.基本用法
<div v-if="showContent">This is content.</div>
data
data() {
return {
showContent: false
}
}
当showContent为false时,不会渲染DOM节点,会留下一个注释标志
showContent为true时,才会渲染DOM节点
2.v-else 二重判断
v-if和v-else配合使用,v-else必须要和v-if相邻,都在会报错
<div>
<div v-if="showContent">This is content.</div>
<div v-else>Hide content.</div>
</div>
3.v-else-if多重判断
当由多重条件时,可以使用v-else-if,类似于v-if的else-if块,v-else元素必须紧跟在带v-if或者v-else-if的元素后面,否则它将不会被识别
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
4.在template中使用v-if
另外,当想切换多个元素时,在<template>上使用v-if可以针对元素进行分组
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
React
react使用与运算符&&、三元运算符(?:)、if else来实现条件渲染的效果
1.与运算符&&
与运算符&&,实现效果类似于v-if,左边变量为真值时,渲染右边的元素
return (
<div>
{showItem && <div>this is content.</div>
</div>
)
2.三元运算符(?:)
使用三元运算符,类似实现v-if,v-else效果,showItem变量为true时显示第一个元素,变量为false时显示第二个元素
return (
<div>
showItem?
(<div>this is true content.</div>):(<div>this is false content</div>)
</div>
)
3.多重判断
当处理多重判断时,可以使用函数加if else多重判断或者switch case的形式来实现,类似v-if、v-else-if、v-else的效果
1)if-else多重判断
render(){
const {type}=this.state;
const toggleShow=(type)=>{
if(type==='A'){
return <div>A</div>;
}else if(type==='B'){
return <div>B</div>;
}else if(type==='C'){
return <div>C</div>;
}else{
return null;
}
}
return (<div>{toggleShow(type)</div>)
}
2)switch case多重判断
render () {
const { type } = this.state;
const toggeleShow = (type) => {
switch (type) {
case 'A':
return <div>A</div>;
case 'B':
return <div>B</div>;
case 'C':
return <div>C</div>;
default:
return null;
}
};
return (
<div>
{toggeleShow(type)}
</div>
);
}
二、是否显示(v-show vs style+class)
另外一个用于展示条件的元素的选项时v-show,react中可以通过style或者切换class的形式实现是否显示
Vue
v-show渲染的元素会被渲染并保留在Dom中,v-show只是简单地切换元素的css属性display
- 当showContent为false时,元素渲染的时候,style值为node,则达到隐藏的效果
- 当showContent为true的时候,style的值为block
- 注意:v-show不支持<template>元素,也不支持v-else
<div v-show="showContent">this is content</div>
v-if
与v-show
对比总结:
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。- 相比之下,
v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 - 一般来说,
v-if
有更高的切换开销,而v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show
较好;如果在运行时条件很少改变,则使用v-if
较好。
React
react中通过修改style或者class来实现v-show类似效果
1.通过修改style属性中的display属性来实现切换是否显示
<div style={{display:showItem?'block':'none'}}>this is content</div>
2.通过变量判断修改class来实现切换效果,本质上也是切换元素的display属性来实现切换效果
在react中动态修改元素样式时(比如切换
tab
、按钮选中状态),适用于使用class
来实现。
const itemClass=showItem?'show-item-class':'hide-item-class';
return (<div className={itemClass}>this is content</div>)
class样式:
.show-item-class {
display: block;
}
.hide-item-class {
display: none;
}
三、列表渲染
vue中使用v-for来渲染列表,react中使用map来渲染列表。不管是v-for还是map来渲染列表都需要添加key值(key在兄弟节点之间必须唯一),方便快速比较新旧虚拟DOM树间的差异。
Vue
vue中可以使用v-for来渲染数组、对象、<template>、组件
1)渲染数组
<div>
<div v-for="(item, index) in items" :key="item.message + index">
{{item.message}}
</div>
</div>
data
data() {
return {
items: [
{
message: 'Hello'
},
{
message: 'Welcome'
}
]
}
}
2)渲染对象
v-for也可以用来遍历一个对象的属性,使用(value,key,index)in obj的形式,其中key表示对象的key值,value表示对象的value值,index表示当前索引
在遍历对象时,采用Object.keys()的结果遍历,但是不能保证它的结果在不通打JavaScript引擎下都一致。
<div v-for="(value,key,index) in obj" :key="key+index">
{{index}}.{{key}}}:{{value}}
</div>
data
data() {
return {
obj: {
name: 'xiaoming',
age: 18,
sex: 'male',
height: 175
}
}
}
3)渲染多个元素
在<template>上使用v-for来渲染一段包含多个元素的内容
在<template>上使用v-for来渲染元素段时,不允许绑定key值,因为template上并不会生成实际的Dom节点,可以给底下的元素绑定key值。
<div>
<template v-for="(item, index) in items">
<div :key="item.message">{{ item.message }}</div>
<div :key="item.message + index" class="divider"></div>
</template>
</div>
data
data() {
return {
items: [
{
message: 'hello'
},
{
message: 'world'
},
{
message: 'welcome'
}
]
}
},
4)渲染自定义组件
在自定义组件上,可以使用v-for渲染自定义组件列表,通过props将数据传递给组件,此时要注意,在组件上使用v-for时,key时必须的
<my-component
v-for="(item, index) in items"
:key="item.message + index"
:item="item">
</my-component>
my-component组件使用props接收父组件传来的数据
<template>
<div>{{item.message}}</div>
</template>
<script>
export default {
props: ['item'],
data() {
return { }
}
}
</script>
React
1)渲染数组
遍历数组中每个元素,得到一组jsx元素列表,数组中的每一个元素需要添加唯一的key值
render(){
const items=[
{message:'hello'},{message:'world'},{messgae:'welcome'}
]
const listItems=items.map((item,index)=> <div key={item.message+index}>{item.messgae}</div>
return (
<div>
{listItems}
</div>
)
}
2)渲染对象
对于对象,可以采用方法通过Object.keys()或者Object.entries()来遍历对象
render(){
const obj = {
name: 'xiaoming',
age: 18,
sex: 'male',
height: 175
};
const renderObj=(obj)=>{
const keys=Object.keys(obj)
return keys.map((item,index)=> <div key={index}>{obj[item]}</div>
};
return (<div> {renderObj}</div>)
}
3)渲染自定义组件列表
渲染自定义组件列表与Vue中类似,需要给组件添加key值标识
render(){
const items = [
{
message: 'hello'
},
{
message: 'world'
},
{
message: 'welcome'
}
];
const ListItems=item.map((item,index)=>
<ListItem message={item.message} key={item.message+index} />;
return (<div>{ListItem}</div>
}
三、计算属性(computed vs useMemo+useCallback)
计算属性表示根据组件的数据(包含组件自身的数据和接收父组件的props),需要二次计算并“保存”的数据,使用计算属性的好处是避免每次重复计算的开销(比如遍历一个巨大的数组并作大量的计算)
Vue
veu中用computed来表示计算属性,可以电话已多个计算属性,计算属性可以互相调用,计算属性是基于 他们的响应式依赖进行缓存的,只有相关响应式依赖发生变化时它们才会重新求值。vue中可以直接使用this.xx获取计算属性。
1)基本用法
下面声明计算属性reverseMessage依赖于message,这就意味着只要message还没有发生改变,多次访问reverseMessage计算属性会立即返回之前的计算结果。
<div>
message: <input type="text" v-model="message" />
<div>{{reversedMessage}}</div>
</div>
script
data() {
return {
message:''
}
},
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
}
2)计算属性的setter
计算属性默认只有getter,也可以使用setter,当修改计算属性的时候,会触发setter回调
script
data() {
return {
message:'',
info: ''
}
},
computed: {
reversedMessage: {
get(){
return this.message.split('').reverse().join('')
},
set(newValue){
this,info=newValue
}
}
},
methods: {
changeMessage(event) {
// 修改reversedMessage计算属性
this.reversedMessage = event.target.value;
}
}
React
react hooks使用useMemo表示memoized的值,使用useCallback表示memoized的回调函数,实现与vue中computed类似的功能
适用场景:子组件使用了PureComponent或者React.memo,那么你就可以考虑使用useMemo和useCallback封装提供给他们非props,这样就能够充分利用这些组件的浅比较能力。
1)useMemo
useMemo返回一个memoized的值,useMemo会依赖某些依赖值,只有在某些依赖项发生改变时才会重新计算memoized的值,如果没有提供依赖项数组,useMemo在每次渲染时都会计算新的值。useMemo可以作为性能优化的手段
传入useMemo的函数会在渲染期间执行,请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect的适用范畴,而不是useMemo。
function NewComponent(props){
const {num}=props;
const [size,setSize]=useState(0);
//max是useMemo返回的一个memoized的值
const max=useMemo(()=> Math.max(num,size),[num,size]);//[num,size]表示依赖项数组
return (<div>
<input
type="number"
value={size}
onChange={(e)=>setSize(e.target.value)} />
<div>Max {max}</div>
</div>
}
2)useCallback
useCallback把内联回调函数以及依赖项数组作为参数传入useCallback ,他将返回该回调函数的memoized版本,该回调函数仅在某个依赖项发生改变时才会更新,当你把回到函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如shouldComponentUpdate的子组件时,它将非常有用)
function NewComponent(props){
const [message,setMessage]=useState('hello world');
const handlChange=useCallback((value)=>{
setMessage(value)
},[]);
return (<div>
<input
type="number"
value={message}
onChange={(e)=>handleChange(e.target.value)} >
<div>{message}</div>
</div>
}
四、侦听器(watch vs getDerivedStateFromProps+componentDidUpdate)
侦听器是指通过监听props或者组件数据(date或state)的变化来执行一些异步或者数据操作
Vue
vue中主要通过watch监听props、data、computed的变化,执行异步或者开销较大的操作
下面ProjectManage组件通watch监听projectId prop的变化获取对应的项目信息
export default {
name: "ProjectManage",
props: ["projectId"],
data() {
return {
projectInfo: null
};
},
watch: {
projectId(newVaue, oldValue) {
if (newVaue !== oldValue) {
this.getProject(newValue);
}
}
},
methods: {
getProject(projectId) {
projectApi
.getProject(projectId)
.then(res => {
this.projectInfo = res.data;
})
.catch(err => {
this.$message({
type: "error",
message: err.message
});
});
}
}
};
React
react通过static getDerivedStateFromProp()和componentDidUpdate()实现监听器的功能
1)static getDerivedStateFromProps()
getDerivedStateFromProps()会在调用render方法之前调用,并且初始化挂载及后续更新时都会被调用,它返回一个对象来更新state,如果返回nul则不更新任何内容
关于getDerivedStateFromProps有以下两点:
- 不管props变化、执行setState或者forceUpdate操作都会在每次渲染前触发次方法
- 当state的值在任何时候都取决于props的时候是用该方法
class NewComponent extends React.component{
constructor(props){
super(props)
this.state={
info:''
}
}
static getDerivedStateFromProps(nextProps,prevState){
if(nextProps,info!==prevState.info){
return {
info:nextProps.info
}
}
return null
}
render(){
const {info}=this.state
return (<div>{info}</div>
}
}
2)componentDidUpdate()
componentDidUpdate()方法在组件更新后被调用,首次渲染不会执行此方法。当组件更新后,可以在此处操作DOM,执行setState或者执行异步请求操作
componentDidUpdate(prevProps,prevState,snapshot)
关于componentDidUpdate有以下4点说明:
- 第三个参数snapshot来源于getSnapshotBeforeUpdate()生命周期的返回值,若没有实现getSnapshotBeforeUpdate(),此参数为undefined
- 可以在componentDidUpdate()中直接调用setState(),但是它必须被包裹在一个条件语句里,否则会导致死循环
- 可以在componentDidUpdate()对更新前后的props进行比较,执行异步操作
- 如果shouldComponentUpdate()返回值为false,则不会调用componenDidUpdate()
class NewComponent extends React.Component{
constructor(props){
super(props);
this.state={
projectInfo:null
}
}
getProject=(projectId)=>{
projectApi.getProject(projectId).then(res=>{
this.projectInfo=res.data
}).catch(err=>{
message.error(err.nessage)
})
}
componentDidUpdate(prevProps){
if(this.props.projectId!==prveProps.projectId){
this.getProject(this.props.projectId)
}
}
render(){
const {projectInfo}=this.state;
return (<React.Fragment>
<div>{projectIno.name}</div>
<div>{projectInfo.detail}</div>
</React.Fragment>
}
}
五、ref
ref用来给元素或者子组件注册应用信息,允许我们访问子组件或者子节点
ref常用于:
- 管理焦点,文本选择或者媒体播放
- 触发强制动画
Vue
通过给组件或者子元素设置ref这个attribute为子组件或者子元素赋予一个ID引用
$ref只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”-----你应该避免在模板或者计算属性中访问$refs
1)子元素引用ref
<div>
<input type="text" v-model="message" ref="inputMessage" />
</div>
加载完毕后输入框自动获取焦点
mounted() {
this.$refs.inputMessage.focus();
}
2)子组件引用ref
子组件引用ref常用于福罪案使用子组件的方法(常用表单验证就是采用这种方式验证的)
<template>
<div>
<el-form ref="createForm" label-width="80px" :model="form" :rules="rules">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email"></el-input>
</el-form-item>
</el-form>
<el-button @click="handleSubmit">提交</el-button>
</div>
</template>
<script>
export default {
name: 'CreateForm',
data() {
return {
form: {
name: '',
email: ''
},
rules: {
name: [{required: true, message: '名称不能为空', trigger: 'blur'}],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
]
}
}
},
methods: {
handleSubmit() {
this.$refs.createForm.validate((valid) => {
console.log(valid);
})
}
}
}
</script>
React
react不像vue中直接给ref传字符串类型值,class组件通过React.createRef绑定ref属性(React v16.0版本之后),函数组件通过useRef绑定ref属性,还可以通过React.forwradRef用于转发ref属性
1)class组件绑定ref
通过React.createRef在构造函数中生成ref,在绑定input元素上,加载完成后自动聚焦
class NewComponent extends React.Component{
construvtor(props){
super(props)
this.state={
message:'hello world'
}
this.inputRef=React.createRef();
}
componentDidMount(){
this.inputRef.current.focus();
}
render(){
const {message}=this.state;
return (<div>
<input
type="number"
ref={this.inputRef}
/>
<div>{message}</div>
</div>
}
}
2)函数组件绑定ref
函数组件可以使用useRef绑定ref属性,useRef返回一个可变的ref对象,其.current属性被初始化为传入的参数(initalValue)。返回的ref对象在组件的整个生命周期内保持不变。
function NewComponent(){
const [message,setMessage]=useSatte('hello world');
const inputRef=useRef(null)
useEffect(()=>{
inputRef.current,focus()
},[])
return (<div>
<input type="number" ref={inputRef} />
<div>{message}</div>
</div>
}
六、表单(v-model vs value)
对于表单,vue使用v-model在表单上实现双向数据绑定,react中通过在表单组件上绑定value属性以受控的组件的形式管理表单数据。
Vue
v-model指令在表单<input>、<textarea>及<select>元素上创建双向数据绑定,它会根据控件类型自动选取正确的方式来更新元素。v-model本质上不过是语法糖
v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件
- text和textarea元素使用value属性和input事件;
- checkbox和radio使用checked属性和change事件;
- select字段将value作为props并将change作为事件;
1)基本用法
1.文本
input输入框上绑定v-model属性绑定msg,当修改input输入值时,msg会自动同步为用户输入值
<div>
<input v-model="msg" />
</div>
v-model写法等价于:value和@input的结合,:value绑定输入值,@input表示接受输入事件修改msg的值为输入的值,从而实现双向数据绑定
<div>
<input :value="msg" @input="e=>(msg=e.target.value)"/>
</div>
2)复选框
单个复选框绑定到布尔值
<div>
<input type="checkbox" v-model="checked" />
<label for="checkbox">{{checked}}</label>
</div>
React
react中,表单元素(<input> 、<select>、<checkbox>)通常维护自己的state,将state赋值给value属性,并根据用户输入通过setState()更新state,以这种方式控制的表单元素称为"受控组件“
1)受控组件
受控组件中,state作为组件“唯一”的数据源,组件还控制着用户操作过程中表单发生的操作
class CreateForm extends React.Component{
constructor(props){
super(props)
this.state={
name:''
}
}
nameChange=(event)=>{//接受事件作为参数
this.setState({
name:event.target.value
})
}
render(){
const {name}=this.state
return (<div>
<input value={name} onChange={this.nameChange} />
<div>name:{name}</div>
</div>
}
}
2)非受控组件
在raact中,对于不能使用state方式的管理的表单组件称为非受控组件,非受控组件的值不能通过代码控制,表单数据交由Dom节点来处理。对于非受控组件,可以使用ref从DOM节点中获取表单数据。
<input type="file" />始终是一个非受控组件,通过创建ref的形式获取文件数据
class CreateForm extends React.Component{
constructor(props){
super(props)
this.fileRef=React.createRef(null)
}
fileChange=(event)=>{
event.preventDefault();
const file=this.fileRef.current.files[0];
console.log(file)
}
render(){
return(
<div>
<input type="file" ref={this.fileRef} onChange={this.fileChange} />
</div>
)
}
}
七、插槽(slot vs Render Props+this.props.children)
vue和react中都实现了“插槽(内容分发)功能”,vue中主要通过slot实现插槽功能,react中通过this.props.children和render Props实现类似Vue中的插槽功能。
Vue
React
react中通过this.props.children和render Props实现类似Vue中的插槽功能。
1)this.props.children
每个组件都可以通过this.props.children获取包含组件开始标签和结束之间的内容,这个与vue中的默认插槽类似。
在class组件中使用this.props.children,在function函数组件中使用props.children。
在class组件中
class NewComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return <div>{this.props.children}</div>
}
}
在function组件中
function NewComponent(props) {
return <div>>{props.children}</div>
}
父组件使用NewComponent组件
<NewComponent>
<h2>This is new component header.</h2>
<div>
This is new component content.
</div>
</NewComponent>
最终html被渲为
<NewComponent>
<h2>This is new component header.</h2>
<div>
This is new component content.
</div>
</NewComponent>
2)render Props
render props是指一种在React组件之间使用的一个值为函数的props共享代码的技术,render prop是一个用于告知组件需要渲染什么内容的函数prop
比如我们常用react-router-dom中的Route的component prop就采用了典型的render props的用法
<Router history={browserHistory}>
<Route path="/" component={Index}> //compnent接受具体的组件
<IndexRoute component={HomePage} />
<Route path="/users" component={Users}/>
</Route>
</Router>
通过多个render prop即可实现类似于vue中具名插槽的功能
NewComponent定义了header、main、footer prop
class NewComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
const { header, main, footer, children } = this.props;
return (<div>
<header>
{header || (<div>Header content.</div>)}
</header>
<main>
{main || (<div>Main content.</div>)}
</main>
{children}
<footer>
{footer || (<div>Footer content.</div>)}
</footer>
</div>);
}
}
父组件向子组件传递render prop
<NewComponent
header={<div>This is header content.</div>}
content={<div>This is main content.</div>}
footer={<div>This is footer content.</div>}>
<div>
This is new component children.
</div>
</NewComponent>
最终html将被渲染为
<div>
<header>
<div>This is header content.</div>
</header>
<main>
<div>This is main content.</div>
</main>
<div>This is new component children.</div>
<footer>
<div>This is footer content.</div>
</footer>
</div>