React 和 Vue 特性和书写差异

阅读 2073
收藏 117
2017-05-22
原文链接:github.com

Vue均使用ES6语法,主要以单文件组件为例,写法上优先使用缩写。

React使用TS语法。

生命周期

  • Vue

vue-lifecyle

  • React

react-lifecyle

入口&根实例

  • Vue
const app = new Vue({
  /* 选项 */
  render: (h) => h(App) // App为根组件
}).$mount('#app')
  • React
ReactDOM.render(
  <App/>, // App为根组件
  document.getElementById('app')
)

组件定义

  • Vue
// 定义组件构造器
var MyComponent = Vue.extend({/* 选项 */})

// 注册全局组件
Vue.component('my-component', {/* 选项 */})
<!-- 单文件组件 -->
<template>
  <div class="my-component">hello</div>
</template>
<script>
  export default {
    /* 选项 */
  }
</script>
  • React
// 无状态组件
const Foo = () => {
  return <div className='foo'></div>
}
// 完整组件
class Foo extends React.Component<{}, void> {
  render () {
    return <div className='foo'>hello</div>
  }
}

组件引用

  • Vue
<!-- 以单文件组件为例:Foo.js -->
<template>
  <div class="Foo">
    <!-- 必须小写,不能自闭合 -->
    <bar></bar>
  </div>
</template>
<script>
  // 引入组件
  import Bar from './Bar'

  export default {
    name: 'Foo',
    components: {Bar}
  }
</script>
  • React
import Bar from './Bar'

class Foo extends React.Component<{}, void> {
  render () {
    return (
      <div className='foo'>
        {/* 组件约定大写开头,可自闭合 */}
        <Bar/>
      </div>
    )
  }
}

组件内部状态

  • Vue
<template>
  <div class="foo">
    <p class='name'>{{name}}</p>
    <p class='age'>
      {{age}}
      <button @click="onAdd">add</button>
    </p>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        name: 'Tom',
        age: 18
      }
    },
    methods {
      onAdd () {
        // 直接修改
        this.age++
      }
    }
  }
</script>
  • React
interface IFooState {
  name: string,
  age: number
}

class Foo extends React.Component<{}, IFooState> {
  state = {
    name: 'tom',
    age: 18
  }

  onAdd = () => {
    // 必须通过setState修改
    this.setState({
      age: this.state.age + 1
    })
  }

  render () {
    const {name, age} = this.state

    return (
      <div className='foo'>
        <p class='name'>{name}</p>
        <p class='age'>
          {age}
          <button onClick={this.onAdd}>add</button>
        </p>
      </div>
    )
  }
}

父子组件通讯

  • Vue
<!-- Parent.vue -->
<template>
  <div class="parent">
    <child name='tom' :age='18' @click="onAdd"></child>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        age: 18
      }
    },
    methods {
      onAdd () {
        this.age++
      }
    }
  }
</script>
<!-- Child.vue -->
<template>
  <div class="child">
    <p class='name'>{{name}}</p>
    <p class='age'>
      {{age}}
      <button @click="onAdd">add</button>
    </p>
  </div>
</template>
<script>
  export default {
    props: {
      name: {type: String},
      age: {type: Number, default: 18}
    },
    methods {
      onAdd () {
        this.$emit('click')
      }
    }
  }
</script>
  • React
interface IChildProps {
  name: string,
  age?: number,
  onAdd?: () => void
}

class Child extends React.Component<IChildProps, void> {
  static defaultProps = {
    age = 18,
    onAdd: () => {}
  }

  render () {
    const {name, age} = this.props

    return (
      <div className='child'>
        <p class='name'>{name}</p>
        <p class='age'>
          {age}
          <button onClick={this.onAdd}>add</button>
        </p>
      </div>
    )
  }
}

interface IParentState {
  age: number
}

class Parent extends React.Component<{}, IParentState> {
  state = {
    age: 18
  }

  onAdd = () => {
    this.setState({
      age: this.state.age + 1
    })
  }

  render () {
    const {name, age} = this.state

    return (
      <div className='parent'>
        <Child name='Tom' age={18} onAdd={this.onAdd}></Child>
      </div>
    )
  }
}

模板/JSX语法

  • Vue
<!-- 可搭配其他模板语言,如Pug等 -->
<template>
  <!-- 变量 -->
  <div>{{name}}</div>
  <!-- 表达式 -->
  <div>{{ ok ? 'YES' : 'NO' }}</div>
  <!-- HTML -->
  <div v-html="rawHtml"></div>
  <!-- 属性:属性名必须小写(kebab-case) -->
  <div id="app"></div>
  <div :id="dynamicId"></div>
  <foo :task-count="18"></foo>
  <foo :class="['item', foo]"></foo>
  <foo :style="{'margin-top': '10px'}"></foo>
  <!-- 事件 -->
  <foo @action="onAction"></foo>
</template>
  • React
render () {
  return (
    <!-- 变量 -->
    <div>{name}</div>
    <!-- 表达式 -->
    <div>{ ok ? 'YES' : 'NO' }</div>
    <!-- HTML -->
    <div dangerouslySetInnerHTML={rawHtml}></div>
    <!-- 属性 -->
    <div id='app'></div>
    <div id={dynamicId}></div>
    <foo taskCount={18}></foo>
    <foo className={'item ' + foo}></foo>
    <foo style={{marginTop: 10}}></foo>
    <!-- 事件 -->
    <foo onAction="onAction"></foo>
  )
}

条件渲染

  • Vue
<template>
  <div v-if="foo">foo</div>
</template>

<template>
  <div v-if="foo">foo</div>
  <div v-else-if="bar">bar</div>
</template>

<template>
  <div v-if="foo">foo</div>
  <div v-else-if="bar">bar</div>
  <div v-else>other</div>
</template>
  • React
render () {
  return foo && <div>foo</div>
}

render () {
  return foo ? <div>foo</div> : <div>bar</div>
}

render () {
  return (
    { foo ? <div>foo</div>
      : bar
      ? <div>bar</div>
      : <div>other</div>
    }
  )
}

列表渲染

  • Vue
<template>
  <div class='list'>
    <div v-for="item in list" :key="item">{{item}}</div>
  </div>
</template>
  • React
render () {
  return (
    <div className='list'>
      {list.map((item) => <div key={item}>{item}</div>)}
    </div>
  )
}
// 或者
render () {
  const items = list.map((item) => <div key={item}>{item}</div>)

  return (
    <div className='list'>
      {items}
    </div>
  )
}

表单&双向绑定

  • Vue
<!-- 表单 -->
<template>
  <form>
    <input v-model="name">
    <!--
      相当于以下的语法糖:
      <input v-bind:value="name" v-on:input="name = $event.target.value">
      在组件中相当于
      <foo v-bind:value="name" v-on:input="name = arguments[0]"></foo>
    -->
  </form>
</template>
<script>
  export default {
    data () {
      return {
        name: ''
      }
    }
  }
</script>
<!-- Vue 2.3.0+ -->

<!-- Parent.vue -->
<template>
  <child :foo.sync="bar"></child>
  <!--
    sync只是语法糖,实际上拓展为:
    <child :foo="bar" @update:foo="val => bar = val"></child>
  -->
</template>

<!-- Child.vue -->
<script>
  export default {
    methods: {
      onChange () {
        this.$emit('update:foo', newValue)
      }
    }
  }
</script>
  • React
interface IFooState {
  name: string
}

class Foo extends React.Component<{}, IFooState> {
  onChange = (e) => {
    const name = e.target.value
    this.setState({name})
  }

  render () {
    const {value} = this.state

    return (
      <div>
        <input value={value} onChange={this.onChange}/>
      </div>
    )
  }
}

内容分发

  • Vue
<!-- Child -->
<template>
  <!-- 必须有根元素 -->
  <div class="child">
    <slot name="header"></slot>
    <slot></slot>
    <slot name="footer"></slot>
  </div>
</template>
<script>
  export default {}
</script>

<!-- Parent -->
<template>
  <div class="parent">
    <child>
      <p slot="header">header</p>
      <p>content</p>
      <p slot="footer">footer</p>
    </child>
  </div>
</template>
<script>
  import Child from './Child'
  export default {
    components: {Child}
  }
</script>
  • React
interface IChildProps {
  header?: React.Node,
  children?: React.Node,
  footer?: React.Node
}

class Child extends React.Component<IChildProps, void> {
  render () {
    const {header, children, footer} = this.props
    return (
      <div className='child'>
        {header}
        {children}
        {footer}
      </div>
    )
  }
}

class Parent extends React.Component<{}, void> {
  render () {
    return (
      <div className='parent'>
        <Child
          className='child'
          header='header'}
          footer={<p>footer</p>}>
          <p>content</p>
        </Child>
      </div>
    )
  }
}
评论