关于Vue和React的一些对比及我的思考(中)

Vue和React都是目前最流行、生态最好的前端框架之一。框架自己没有优劣之分,只有适用之别,选择符合自身业务场景、团队基础的技术才是咱们最主要的目的。css

博主1年前用的是Vue框架,近半年转技术栈到React框架,对于Vue和React都有一些基本的了解。接下来博主将与你们经过上、中、下三部一块儿走近Vue和React,共同探讨它们的差别。(好比你正在用vue,对react感兴趣也能够看下,反之亦然)html

总体内容概览:前端

总体内容概览

中部将主要从条件渲染是否显示列表渲染计算属性侦听器ref表单插槽八个方面对比vue和react,欢迎多多交流,共同探讨,感谢支持。vue

上部连接:关于Vue和React的一些对比及我的思考(上)react

9.条件渲染(v-if vs &&)

条件渲染用于根据条件判断是否渲染一块内容。数组

vue

vue中用v-if指令条件性地渲染一块内容。只有当表达式返回真值时才被渲染,v-if还能够和v-else-ifv-else配合使用,用于不一样条件下的渲染,相似js里的if else语法。缓存

(1)基本用法

<div v-if="showContent">This is content.</div>
复制代码

data性能优化

data() {
    return {
      showContent: false
    }
  }
复制代码

showContentfalse时,不会渲染DOM节点,会留下一个注释标志。bash

v-if为false

showContenttrue时,才会渲染DOM节点。前端框架

v-if为true

(2)v-else 二重判断

v-ifv-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-ifelse-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的效果。

Ⅰ.if-else多重判断

render() {
    const { type } = this.state;
    const toggeleShow = (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>
        {toggeleShow(type)}
      </div>
    );
  }
复制代码

Ⅱ.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>
    );
  }
复制代码

10.是否显示(v-show vs style+class)

另外一个用于展现条件的元素的选项是v-show,react中能够经过style或者切换class的形式实现是否显示。

vue

v-show渲染的元素会被渲染并保留在DOM中。v-show只是简单地切换元素的css属性display

<div v-show="showContent">This is content.</div>
复制代码

showContentfalse时,styledisplay属性值为none

v-show为false

showContenttrue时,styledisplay属性值为block(元素默认display属性值)。

v-show为true

注意,v-show 不支持 <template> 元素,也不支持 v-else。

v-ifv-show对比总结:

  • 1)v-if是“真正”的条件渲染,由于它会确保在切换过程当中条件块内的事件监听器和子组件适当地被销毁和重建。
  • 2)v-if也是惰性的:若是在初始渲染时条件为假,则什么也不作——直到条件第一次变为真时,才会开始渲染条件块。
  • 3)相比之下,v-show就简单得多——无论初始条件是什么,元素老是会被渲染,而且只是简单地基于 CSS 进行切换。
  • 4)通常来讲,v-if有更高的切换开销,而v-show有更高的初始渲染开销。所以,若是须要很是频繁地切换,则使用v-show较好;若是在运行时条件不多改变,则使用v-if较好。

react

react中经过修改style或者class来实现v-show相似效果。

经过修改style属性的display属性来实现切换是否显示。

<div style={{ display: showItem ? 'block' : 'none' }}>
    This is content.
</div>
复制代码

经过变量判断修改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;
}
复制代码

11.列表渲染(v-for vs map)

vue中使用v-for来渲染列表,react中使用map来渲染列表。不论是v-for仍是map来渲染列表都须要添加key值(key在兄弟节点之间必须惟一),方便快速比较出新旧虚拟DOM树间的差别。

vue

vue中可使用v-for来渲染数组、对象、<template>、组件。

(1)渲染数组

渲染数组时,使用(item, index) in items形式的特殊语法,其中items是源数据数组,item则是被迭代的数组元素的别名,index表示当前元素的索引。

<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'
       }
     ]
    }
  },
复制代码

生成DOM时,并不会生成实际DOM节点。

template使用v-for

(4)渲染自定义组件列表

在自定义组件上,可使用v-for渲染自定义组件列表,经过props将数据传递给组件。

在组件上使用v-for时,key是必须的。

v-for渲染自定义组件列表,将item经过props传递给组件。

<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

react中使用map()方法来实现列表渲染。

(1)渲染数组

遍历数组中的每一个元素,获得一组jsx元素列表。数组中的每个元素须要添加惟一的key值。

render() {
    const items = [
      {
        message: 'hello'
      },
      {
        message: 'world'
      },
      {
        message: 'welcome'
      }
    ];
    const listItems = items.map((item, index) => <div key={item.message + index}>{item.message}</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(obj)}
      </div>
    );
  }
复制代码

(3)渲染自定义组件列表

渲染自定义组件列表与vue中相似,须要给组件添加key值标识。

render() {
    const items = [
      {
        message: 'hello'
      },
      {
        message: 'world'
      },
      {
        message: 'welcome'
      }
    ];
    const listItems = items.map((item, index) => 
          <ListItem message={item.message} key={item.message + index} />);
    return (
      <div>
        {listItems}
      </div>
    );
  }
复制代码

12.计算属性(computed vs useMemo+useCallback)

计算属性表示根据组件的数据(包含组件自身的数据和接收父组件的props)须要二次计算并“保存”的数据,使用计算属性的好处是避免每次重复计算的开销(好比遍历一个巨大的数组并作大量的计算)。

vue

vue中用computed来表示计算属性,能够定义多个计算属性,计算属性能够互相调用。计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会从新求值。vue中能够直接使用this.xxx直接获取到计算属性。

(1)基本用法

下面声明计算属性reversedMessage依赖于message,这就意味着只要 message尚未发生改变,屡次访问reversedMessage计算属性会当即返回以前的计算结果。

<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() { // get回调
        return this.message.split('').reverse().join('')
      },
      set(newValue) { // set回调
        this.info = newValue
      }
    }
  },
  methods: {
    changeMessage(event) {
      // 修改reversedMessage计算属性
      this.reversedMessage = event.target.value;
    }
  }
复制代码

react

react hooks使用useMemo表示memoized的值,使用useCallback表示memoized的回调函数,实现与vue中computed相似的功能。

适用场景:子组件使用了PureComponent或者React.memo,那么你能够考虑使用useMemouseCallback封装提供给他们的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]);
  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 handleChange = useCallback((value) => {
    setMessage(value);
  }, []);
  return (<div>
    <input
      type="number"
      value={message}
      onChange={(e) => handleChange(e.target.value)} />
    <div>{message}</div>
  </div>);
}
复制代码

13.侦听器(watch vs getDerivedStateFromProps + componentDidUpdate)

侦听器是指经过监听props或者组件数据(datastate)的变化来执行一些异步或者数据操做。

vue

vue中主要经过watch监听propsdatacomputed(计算属性)的变化,执行异步或开销较大的操做。

下面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 getDerivedStateFromProps()componentDidUpdate()实现监听器的功能。

(1)static getDerivedStateFromProps()

getDerivedStateFromProps会在调用render方法以前调用,而且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state,若是返回null则不更新任何内容。

关于getDerivedStateFromProps有2点说明:

  • 1)不论是props变化、执行setState或者forceUpdate操做都会在每次渲染前触发此方法。
  • 2)当state的值在任什么时候候都取决于props的时候适用该方法。
class NewComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      info: ''
    }
  }
  static getDerivedStateFromProps(nextProps, prevState) {
    // state中的info根据props中的info保持同步
    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点说明:

  • 1)componentDidUpdate()的第三个参数snapshot参数来源于getSnapshotBeforeUpdate()生命周期的返回值。若没有实现getSnapshotBeforeUpdate(),此参数值为undefined
  • 2)能够在componentDidUpdate()中直接调用setState(),可是它必需被包裹在一个条件语句里,不然会致使死循环。
  • 3)能够在componentDidUpdate()对更新先后的props进行比较,执行异步操做。
  • 4)若是shouldComponentUpdate()返回值为false,则不会调用componentDidUpdate()

下面NewComponent组件在componentDidUpdate()里判断

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.message);
      });
  }
  componentDidUpdate(prevProps) {
    if (this.props.projectId !== prevProps.projectId) {
      this.getProject(this.props.projectId);
    }
  }
  render() {
    const { projectInfo } = this.state;
    return <React.Fragment>
      <div>{projectInfo.name}</div>
      <div>{projectInfo.detail}</div>
      </React.Fragment>
  }
}
复制代码

14.ref

ref用来给元素或子组件注册引用信息,容许咱们访问子组件或者子节点。

ref经常使用于:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。

vue

经过给组件或者子元素设置ref这个attribute为子组件或者子元素赋予一个ID引用。

$refs只会在组件渲染完成以后生效,而且它们不是响应式的。这仅做为一个用于直接操做子组件的“逃生舱”——你应该避免在模板或计算属性中访问$refs

(1)子元素引用ref

子元素上引用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.3版本以后),函数组件经过useRef绑定ref属性,还可使用React.forwardRef用于转发ref属性到子组件中。

(1)class组件绑定ref

经过React.createRef在构造函数中生成ref,在绑定到input元素上,加载完成后自动聚焦。

class NewComponent extends React.Component {
  constructor(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属性被初始化为传入的参数(initialValue)。返回的ref 对象在组件的整个生命周期内保持不变。

function NewComponent() {
  const [message, setMessage] = useState('hello world.');
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (<div>
    <input type="number" ref={inputRef} />
    <div>{message}</div>
  </div>);
}
复制代码

(3)React.forwardRef 转发 ref 到子组件

React.forwardRef会建立一个React组件,这个组件可以将其接受的ref属性转发到其组件树下的另外一个组件中。

这种技术并不常见,但在如下两种场景中特别有用:

  • 转发refsDOM组件
  • 在高阶组件中转发refs

父组件直接传递ref属性给子组件NewComponent

function Parent() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return <div>
     <NewComponent ref={inputRef}><h2>This is refs.</h2></NewComponent>
  </div>;
}
复制代码

子组件使用React.forwardRef 接受渲染函数做为参数,父组件加载完成后聚焦到input输入框。

const NewComponent = React.forwardRef((props, ref) => (<div>
  <input type="number" ref={ref} />
  <div>{props.children}</div>
</div>));
复制代码

15.表单(v-model vs value)

对于表单,vue中使用v-model在表单组件上实现双向数据绑定,react中经过在表单组件上绑定value属性以受控组件的形式管理表单数据。

vue

v-model指令在表单<input><textarea><select>元素上建立双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model本质上不过是语法糖。

v-model在内部为不一样的输入元素使用不一样的属性并抛出不一样的事件:

  • texttextarea元素使用value属性和input事件;
  • checkboxradio使用checked属性和change事件;
  • select字段将value做为prop并将change做为事件。

(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>
复制代码
Ⅱ.复选框

单个复选框绑定到布尔值

<div>
  <input type="checkbox" v-model="checked" />
  <label for="checkbox">{{ checked }}</label>
</div>
复制代码

多个复选框绑定到数组

<div>
    <input type="checkbox" id="apple" value="apple" v-model="selectedFruits" />
    <label for="jack">apple</label>
    <input
      type="checkbox"
      id="banana"
      value="banana"
      v-model="selectedFruits"
    />
    <label for="john">banana</label>
    <input type="checkbox" id="mango" value="mango" v-model="selectedFruits" />
    <label for="mango">mango</label>
    <div>Selected fruits: {{ selectedFruits }}</div>
  </div>
复制代码

data

data() {
    return {
      selectedFruits: []
    };
  }
复制代码
Ⅲ.选择框

1)选择框单选时,绑定字符串

<div>
  <select v-model="selected">
    <option
      v-for="option in options"
      :value="option.value"
      :key="option.value"
    >
      {{ option.text }}
    </option>
  </select>
  <div>Selected: {{ selected }}</div>
</div>
复制代码

data

data() {
  return {
    selected: "A",
    options: [
      { text: "One", value: "A" },
      { text: "Two", value: "B" },
      { text: "Three", value: "C" }
    ]
  };
}
复制代码

2)选择框多选时,绑定的是数组

<div>
  <select v-model="selected" multiple>
    <option
      v-for="option in options"
      :value="option.value"
      :key="option.value"
    >
      {{ option.text }}
    </option>
  </select>
  <div>Selected: {{ selected }}</div>
</div>
复制代码

data

data() {
  return {
    selected: ["A"], //多选时绑定的是数组
    options: [
      { text: "One", value: "A" },
      { text: "Two", value: "B" },
      { text: "Three", value: "C" }
    ]
  };
}
复制代码

####(2)修饰符 vue对于v-model扩展了.lazy.number.trim修饰符加强功能。

Ⅰ.lazy

在默认状况下,v-model在每次input事件触发后将输入框的值与数据进行同步。添加了.lazy修饰符,转变为change事件进行同步。

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
复制代码
Ⅱ.number

.number修饰符能够自动将用户的输入转换为数值类型,尤为是处理数字类型表单项时尤为有用。

<input type="number" v-model.number="num" />
复制代码
Ⅲ.trim

.trim修饰符能够自动过滤用户输入的首尾空白字符。

<input v-model.trim="msg" />
复制代码

(3)自定义组件使用v-model

一个组件上的v-model默认会利用名为valueprop和名为 input的事件。

InputMessage绑定v-model值为msg

<InputMessage v-model="msg" />
复制代码

InputMessage组件经过value props接收值,emit input事件给父组件,修改父组件中msg的值。

<template>
  <input v-model="message" @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
  name: "InputMessage",
  props: ["value"],
  data() {
    return {
      message: this.value
    };
  }
};
复制代码

(4)不能使用v-model管理的表单组件(file等)

对于像<input type="file" />这种类型的表单组件,不能使用v-model管理组件数据,能够经过refs管理表单组件的数据,这点和react中的非受控组件一致。

<template>
  <div>
    <input type="file" ref="file" @change="fileChange" />
  </div>
</template>

<script>
export default {
  name: "InputFile",
  data() {
    return {};
  },
  methods: {
    fileChange() {
      const file = this.$refs.file.files[0];
      console.log(file);
    }
  }
};
</script>
复制代码

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)非受控组件

在react中,对于不能使用state方式管理的表单组件称为非受控组件,非受控组件的值不能经过代码控制,表单数据交由DOM节点来处理。对于非受控组件,可使用refDOM节点中获取表单数据。

<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>)
  }
}
复制代码

16.插槽(slot vs Render Props+this.props.children)

vue和react中都实现了“插槽”(内容分发)功能,vue中主要经过slot实现插槽功能,react中经过this.props.childrenRender props实现相似vue中的插槽功能。

vue

vue中经过<slot>实现插槽功能,包含默认插槽、具名插槽、做用域插槽。

(1)默认插槽

默认插槽使用<slot></slot>在组件内预留分发内容的“占位”,在组件起始标签和结束标签能够包含任何代码,例如html或者其余组件。

关于默认插槽,有2点说明:

(1)若是不使用插槽,插入到组件中的内容不会渲染。
(2)插槽能够设置后备内容,在不插入任何内容时显示。

使用默认插槽的组件:

<template>
  <div>
    <h2>Slot:</h2>
     <slot>Default content.</slot> // 使用slot预留插槽占位,slot中的内容做为后备内容
  </div>
</template>

<script>
export default {
  name: "SlotComponent",
  data() {
    return {};
  }
};
</script>
复制代码

父组件使用该组件,在组件起始标签和结束标签添加插槽内容。

<slot-component>
  <h2>This is slot component.</h2>
</slot-component>
复制代码

最终插槽内容会被插入到组件<slot></slot>占位的位置。

<div>
    <h2>Slot:</h2>
    <h2>This is slot component.</h2>
  </div>
复制代码

<slot-component>没有添加插槽内容时,会渲染默认插槽内容。

<div>
    <h2>Slot:</h2>
    Default content.
  </div>
复制代码

(2)具名插槽

默认插槽只能插入一个插槽,当插入多个插槽时须要使用具名插槽。slot元素有一个默认的attribute name,用来定义具名插槽。

默认插槽的namedefault

在向具名插槽提供内容的时候,咱们能够在一个 <template>元素上使用v-slot指令,并以v-slot的参数的形式提供其名称,将内容插入到对应的插槽下。

v-slot:也能够简写为#,例如v-slot:footer能够被重写为#footer

插槽组件slot-componentheader footer两个具名插槽和一个默认插槽。

<template>
  <div>
    <header>
      <slot name="header">
        Header content.
      </slot>
    </header>
    <main>
      <slot>
        Main content.
      </slot>
    </main>
    <footer>
      <slot name="footer">
        Footer content.
      </slot>
    </footer>
  </div>
</template>
复制代码

向插槽中分别插入内容:

<slot-component>
  <template v-slot:header>
    <div>This is header content.</div>
  </template>
  <template>
    <div>This is main content.</div>
  </template>
  <template #footer>
    <div>This is footer content.</div>
  </template>
</slot-component>
复制代码

最终html会被渲染为:

<div>
  <header>
    <div>This is header content.</div>
  </header>
  <main>
    <div>This is main content.</div>
  </main>
  <footer>
    <div>This is footer content.</div>
  </footer>
</div>
复制代码

(3)做用域插槽

有时候咱们须要在父组件中显示插槽组件的数据内容,这时候做用域插槽就派上用场了。

做用域插槽须要在<slot>元素上绑定attribute,这被称为插槽prop。在父级做用域中,可使用带值的v-slot来定义咱们提供的插槽prop的名字。

插槽组件

<template>
  <div>
    <header>
      <slot name="header">
        Header content.
      </slot>
    </header>
    <main>
      <slot :person="person">
        {{ person.name }}
      </slot>
    </main>
  </div>
</template>

<script>
export default {
  name: "SlotComponent",
  data() {
    return {
      person: {
        name: "xiaoming",
        age: 14
      }
    };
  },
  methods: {}
};
</script>
复制代码

父组件做用域将包含全部插槽prop的对象命名为slotProps,也可使用任意你喜欢的名字。

<slot-component>
  <template slot="header">
    <div>This is header content.</div>
  </template>
  // 使用带值的`v-slot`来定义插槽`prop`的名字
  <template v-slot:default="slotProps"> 
    <div>{{ slotProps.person.name }}</div>
    <div>{{ slotProps.person.age }}</div>
  </template>
</slot-component>
复制代码

最终html将被渲染为:

<div>
  <header>
    <div>This is header content.</div>
  </header>
  <main>
    <div>xiaoming</div>
    <div>14</div>
  </main>
</div>
复制代码

react

react中经过this.props.childrenRender props实现相似vue中的插槽功能。

(1)this.props.children

每一个组件均可以经过this.props.children获取包含组件开始标签和结束标签之间的内容,这个与vue中的默认插槽相似。

class组件中使用this.props.children,在function组件中使用props.childrenclass组件使用this.props.children获取子元素内容。

class NewComponent extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <div>{this.props.children}</div>
  }
}
复制代码

function组件使用props.children获取子元素内容。

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将被渲染为:

<div>
  <h2>This is new component header.</h2>
  <div>This is new component content.</div>
</div>
复制代码

(2)Render props

render prop是指一种在React组件之间使用一个值为函数的prop共享代码的技术。render prop是一个用于告知组件须要渲染什么内容的函数prop

好比咱们经常使用的react-router-dom中的Routecomponent prop就采用了典型的render prop的用法。

<Router history={browserHistory}>
    <Route path="/" component={Index}> // component props接收具体的组件
      <IndexRoute component={HomePage} />
      <Route path="/users" component={Users} />
    </Route>
  </Router>
复制代码

经过多个render prop便可实现相似vue中具名插槽的功能。

NewComponent定义了headermainfooter 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>
复制代码

结语

以上就是博主关于react和vue的一些对比以及我的思考的中部,以为有收获的能够关注一波,点赞一波,码字不易,万分感谢,后续下部会尽快持续更新,感谢关注。

相关文章
相关标签/搜索