bczFE / articles

📚 百词斩前端写文章的地方
8 stars 1 forks source link

从 v-语法 到 jsx:给 vue 用户的 react 小例子 #7

Open gaoryrt opened 6 years ago

gaoryrt commented 6 years ago

这篇只记录了几个 vue 语法糖在 react jsx 中的实现,可以稍微直观对比一下二者的异同 (越发感觉 react 的定位和 vue 不同,react 比 vue 还要 view 一点 (当然 react 的写法很自由,我这里就不列举了 (我也是渣渣,有不对的地方麻烦指出来,共同学习

gaoryrt commented 6 years ago

声明式渲染

// vue
<div id="app">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

// react component
class App extends Component {
  constructor() {
    super()
    this.state = {
      message: 'Hello React!'
    }
  }

  render() {
    return (
      <div id="app">
        { this.state.message }
      </div>
    )
  }
}

// react stateless component
function App() {
  const [message, _] = useState('Hello React!')
  return (
    <div id="app">
      {message}
    </div>
  )
}
gaoryrt commented 6 years ago

声明式渲染

// vue
<div id="app-2">
  <span v-bind:title="message">
    鼠标悬停几秒钟查看此处动态绑定的提示信息!
  </span>
</div>
var app2 = new Vue({
  el: '#app-2',
  data: {
    message: '页面加载于 ' + new Date().toLocaleString()
  }
})

// react component
class App extends Component {
  constructor() {
    super()
    this.state = {
      message: '页面加载于 ' + new Date().toLocaleString()
    }
  }

  render() {
    return (
      <div id="app-2">
        <span title={ this.state.message }>
          鼠标悬停几秒钟查看此处动态绑定的提示信息!
        </span>
      </div>
    )
  }
}

// react stateless comp
function App() {
    const [message, _] = useState('页面加载于 ' + new Date().toLocaleString())
    return <div id="app-2">
        <span title={message}>
          鼠标悬停几秒钟查看此处动态绑定的提示信息!
        </span>
    </div>
}
gaoryrt commented 6 years ago

条件渲染

// vue
<div id="app-3">
  <p v-if="seen">现在你看到我了</p>
</div>
var app3 = new Vue({
  el: '#app-3',
  data: {
    seen: true
  }
})

// react comp
class App extends Component {
  constructor() {
    super()
    this.state = {
      seen: false
    }
  }

  render() {
    return (
      <div id="app-3">
      {
        this.state.seen &&      //   this.state.seen ?
        <p>                     //   <p>
          现在你看到我了           //     现在你看到我了
        </p>                    //   </p> : null
      }
      </div>
    )
  }
}

// react stateless comp
function App() {
    const [seen, _] = useState(false)
    return <div id="app-3">
        {seen && <p>现在你看到我了</p>}
    </div>
}
gaoryrt commented 6 years ago

循环渲染

// vue
<div id="app-4">
  <ol>
    <li v-for="todo in todos">
      {{ todo.text }}
    </li>
  </ol>
</div>
var app4 = new Vue({
  el: '#app-4',
  data: {
    todos: [
      { text: '学习 JavaScript' },
      { text: '学习 Vue' },
      { text: '整个牛项目' }
    ]
  }
})

// react comp
class App extends Component {
  constructor() {
    super()
    this.state = {
      todos: [
        { text: '学习 JavaScript' },
        { text: '学习 React' },
        { text: '整个牛项目' }
      ]
    }
  }

  render() {
    return (
      <div id="app-4">
      {
        this.state.todos.map(todo =>
          <li>{ todo.text }</li>
        )
      }
      </div>

    )
  }
}

// react stateless comp
function App() {
    const [todos, _] = useState([
        { text: '学习 JavaScript' },
        { text: '学习 React' },
        { text: '整个牛项目' }
    ])
    return <div id="app-4">
        {todos.map(todo =>
            <li>{todo.text}</li>
        )}
    </div>
}
gaoryrt commented 6 years ago

处理用户输入

// vue
<div id="app-5">
  <p>{{ message }}</p>
  <button v-on:click="reverseMessage">逆转消息</button>
</div>
var app5 = new Vue({
  el: '#app-5',
  data: {
    message: 'Hello Vue.js!'
  },
  methods: {
    reverseMessage: function () {
      this.message = this.message.split('').reverse().join('')
    }
  }
})

// react comp
class App extends Component {
  constructor() {
    super()
    this.state = {
      message: 'Hello React.js!'
    }
  }

  reversMessage() {
    this.setState(state => ({
      message: state.message.split('').reverse().join('')
    }))
  }

  render() {
    return (
      <div id="app-5">
        <p>{ this.state.message }</p>
        <button onClick={ () => this.reversMessage() }>逆转消息</button>
      </div>
    )
  }
}

// react stateless comp
function App() {
    const [message, setMessage] = useState('Hello React.js!')
    return <div id="app-5">
        <p>{message}</p>
        <button onClick={_ => setMessage(message.split('').reverse().join(''))}>
            逆转消息
        </button>
    </div>
}
gaoryrt commented 6 years ago

处理用户输入

// vue
<div id="app-6">
  <p>{{ message }}</p>
  <input v-model="message">
</div>
var app6 = new Vue({
  el: '#app-6',
  data: {
    message: 'Hello Vue!'
  }
})

// react comp
class App extends Component {
  constructor() {
    super()
    this.state = {
      message: 'Hello React!'
    }
  }

  handleChange(e) {
    this.setState({message: e.target.value})
  }

  render() {
    return (
      <div id="app-6">
        <p>{ this.state.message }</p>
        <input
          value={ this.state.message }
          onChange={ e => this.handleChange(e) }
        />
      </div>
    )
  }
}

// react stateless comp
function App() {
    const [message, setMessage] = useState('Hello React!')
    return <div id="app-6">
        <p>{ message }</p>
        <input
          value={ message }
          onChange={ e => setMessage(e.target.value) }
        />
    </div>
}
gaoryrt commented 6 years ago

组件化应用构建

<div id="app-7">
  <ol>
    <todo-item
      v-for="item in groceryList"
      v-bind:todo="item"
      v-bind:key="item.id">
    </todo-item>
  </ol>
</div>
Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})

var app7 = new Vue({
  el: '#app-7',
  data: {
    groceryList: [
      { id: 0, text: '蔬菜' },
      { id: 1, text: '奶酪' },
      { id: 2, text: '随便其它什么人吃的东西' }
    ]
  }
})

const TodoItem = ({text}) =>
  <li>{ text }</li>

class App extends Component {
  constructor() {
    super()
    this.state = {
      groceryList: [
        { id: 0, text: '蔬菜' },
        { id: 1, text: '奶酪' },
        { id: 2, text: '随便其它什么人吃的东西' }
      ]
    }
  }

  render() {
    return (
      <div id="app-7">
        <ol>
        {
          this.state.groceryList.map(({ id, text })=>
            <TodoItem key={ id } text={ text }></TodoItem>
          )
        }
        </ol>
      </div>
    )
  }
}
ghost commented 6 years ago

顶一个!

不太会Vue, @gaoryrt 还请补充Vue实现类似功能的对比。(不知道是不是主题之外了,全当增加活跃度来了,有错地方还请补充 = =! )

  1. 回调式Refs或者createRef对子组件或者对原生node的引用
  2. 代码切割和动态加载
  3. 高阶组件
  4. 生命周期
class App extends Component {
    constructor () {
      super()
      this.inputRef = React.createRef()
    }

    componentDidMount () {
      this.inputRef.current.focus()
    }

    render () {
      return (
        <form>
          <input ref={this.inputRef} />
        </form>
      )
    }
}
const Sub1= React.lazy(() => import('./Sub1'))
const Sub2 = React.lazy(() => import('./Sub2'))

class App extends Component {
    render () {
      return (
        <div>
          <React.Suspense fallback={<div>Loading</div>}>
            <Router>
              <Switch>
                <Route path='/sub1' component={Sub1} />
                <Route path='/sub2' component={Sub2} />
              </Switch>
            </Router>
          </React.Suspense>
        </div>
      )
    }
}
function withHOC(WrappedComponent) {
    return calss extends Component {
        // do anything you want

        render () {
            const { extra, ...otherProps } = this.props
            const newProp = fromStateOrMethod
            return (
                <WrappedComponent
                    newProp={newProp}
                    {...otherProps}
                />
            )
        }
    }
}
class App extends Component {
    constructor() {
      // 初始化,设置state等
    }

    componentWillMount() {
      // 第一次render之前调用
    }

    componentDidMount () {
      // 第一次render之后调用, 在这里进行异步操作
    }

    componentWillReceiveProps (nextProps) {
      // 父组件的状态发生变化会子组件会调用该方法,nextProps是传递过来新props
    }

    shouldComponentUpdate () {
      // 决定是否更新,建议使用:PureComponent
    }

    componentWillUpdate () {
      // 更新前调用
    }

    componentDidUpdate () {
      // 更新render之后调用, 不要在这里进行setState, 会导致死循环
    }

    componentDidCatch () {
      // 子组件的异常会倍这个方法捕获,但不能捕获自己组件产生的异常。
      // 可以定义一个ErrorBoundary组件在最外层,将捕获的错误统一处理(例如可以:sentry到服务器)
    }

    componentWillUnmount () {
      // 组件unmount之前会调用
    }

    render () {
      // 渲染
    }
}
gaoryrt commented 6 years ago

子组件引用

<div id="ref-demo">
  <input ref="inputRef">
</div>
var refdemo = new Vue({
  el: '#ref-demo',
  mounted() {
    this.$nextTick(  // mounted + $nextTick 对应 componentDidMount
      () => this.$refs.inputRef.focus()
    )
  }
})

callback ref

class App extends Component {
  componentDidMount () {
    this._inputRef.focus()
  }

  render () {
    return (
      <input ref={ n => this._inputRef = n } />
    )
  }
}

createRef

class App extends Component {
  constructor () {
    super()
    this.inputRef = React.createRef()
  }

  componentDidMount () {
    this.inputRef.current.focus()
  }

  render () {
    return (
      <input ref={ this.inputRef } />
    )
  }
}
gaoryrt commented 6 years ago

插一句

本篇主题是 v-语法 到 jsx,旨在使用 react 实现一些 vue 封装的语法糖 这样来说,具有相似 api 的 子组件引用高阶组件 都能直观看出写法异同 但是由于 vue 高度封装的原因,实现其他两个例子确实不如自由的 react 来得简洁

下面的几个就相当于用 vue 实现 react 了

gaoryrt commented 6 years ago

路由 + 异步 loading

路由有 vue-router,异步组件 loading 有 高级异步组件的工厂函数 vue-router 可以匹配路由,代码分割,动态加载,可惜不能匹配 loading 高级异步组件可以代码分割,动态加载,匹配 loading,但是不能匹配路由 这里就需要根据需求自己实现了


const Sub1= React.lazy(() => import('./Sub1'))
const Sub2 = React.lazy(() => import('./Sub2'))

class App extends Component {
    render () {
      return (
        <div>
          <React.Suspense fallback={<div>Loading</div>}>
            <Router>
              <Switch>
                <Route path='/sub1' component={Sub1} />
                <Route path='/sub2' component={Sub2} />
              </Switch>
            </Router>
          </React.Suspense>
        </div>
      )
    }
}

Vue.use(vueRouter)

const router = new vueRouter({
  routes: [
    { path: '/sub1', component: () => import('./sub1') },
    { path: '/sub2', component: () => import('./sub2') }
  ]
})

const app = new Vue({
  data: { loading: false },
  template: `<div>
              <div v-if="loading">loading</div>
              <router-view v-else />
            </div>`,
  router
}).$mount('#app')

router.beforeEach((to, from, next) => {
  app.loading = true
  next()
})

router.afterEach(() => {
  app.loading = false
})
gaoryrt commented 6 years ago

高阶组件

vue 里面最接近的语法应该是 mixin 但是 react mixin 都被废弃挺久的了 这里就需要根据需求自己实现了

function withHOC(WrappedComponent) {
    return calss extends Component {
        // do anything you want

        render () {
            const { extra, ...otherProps } = this.props
            const newProp = fromStateOrMethod
            return (
                <WrappedComponent
                    newProp={newProp}
                    {...otherProps}
                />
            )
        }
    }
}

function withHOC(WrappedComponent) {
  const { extra, ...otherProps } = WrappedComponent.props
  const newProp = fromStateOrMethod
  return {
    props: otherProps.concat(newProp),
    render (h) {
      return h(WrappedComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      })
    }
  }
}

更多讨论:Discussion: Best way to create a HOC 包括 slot 的实现:探索Vue高阶组件

gaoryrt commented 6 years ago

生命周期

react vue
constructor data() 里 return 前
componentWillMount beforeMount(){}
render templaterender(){}<component>
componentDidMount mounted(){}
componentWillReceiveProps watch 里手动绑定
shouldComponentUpdate
componentWillUpdate beforeUpdate(){}
componentDidUpdate updated(){}
componentDidCatch
componentWillUnmount beforeDestroy(){}
gaoryrt commented 6 years ago

点错关闭了。。。

leechan commented 6 years ago

期待更深入的分析

gaoryrt commented 5 years ago

话说 hook 出来了有些地方可以好好改改

gaoryrt commented 5 years ago

一篇类似的文章: https://sebastiandedeyne.com/react-for-vue-developers/