Life is too short to waste a second

React 入门,结合 Vue 理解

前言

React 就不必多说了,前端的三大框架之一。目前前端情况大概是国内 Vue 比较多,国外 React 比较多。值得一提的是,在 Google 指数中,已美国为例,Vue.js 和 React.js 能算55开,而国内百度指数 Vue.js 则领先 React.js 不少。

既然 Vue 已经在国内成为了主流,我们是否还需要学习 React 呢?个人认为我们应该尽可能的去接触各种技术,而不是局限于一种。所以我最近也花了几天时间学习 React。那么下面开始做笔记了。

前置知识

下面的内容可能需要以下知识来帮助理解:

注:代码块中模板字符串的 ` 会被转换为 code 标签,为了编写的方便暂时没有进行转义处理

函数式组件

Hello, World!

虽然 React 的官方文档是使用 Class 生成组件的,不过这并不影响我们愉快的使用函数式组件和 Hooks

先来看看 React 是怎么 hello world 的。

import React from 'react'

// React.FC: Function Component,我们需要告诉 TS 这是一个函数组件,否则你就不能愉快的编码了
// 这里使用箭头函数给 App 赋值
const App: React.FC = () => {
  // return 里面的内容就相当于 Vue 中的 template,这是 JSX 语法,可能一开始还不熟悉
  return (
    <h1>Hello, world!</h1>
  )
}

export default App

好了,我们已经编写了一个 App 组件,它会显示 Hello, World! 那么 React 是如何让其渲染到 DOM 的呢?这里我们已 Create React App 脚手架为例子看看,虽然 React 能够通过 HTML 运行,不过作为老道的切图仔当然是要已项目的规格来学习。

// index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(
  <App />,
  document.getElementById('root')
)

ReactDOM 的 render 函数将 App 组件渲染到了 id 为 root 的 DOM EL 中,是不是很熟悉呢?反手来看看 Vue 的

// main.js
import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')

Vue 将 App 挂载到了 id 为 app 的 DOM EL 中,在这个方面,React 和 Vue 没有太大的区别

JSX

我们在前面的例子中提到了 JSX ,下面来简单看看 JSX 的使用

原生 HTML

const App: React.FC = () => {
  return (
    <h1>Hello, world!</h1>
  )
}

HTML 中插入 JS,和 Vue 再模板中插入值一样,不过 React 使用的单大括号来表示 JS 内容,并且自由度极高。你只需要记住,大括号中的会被当做 JS 处理即可。

const App: React.FC = () => {
  const name = 'jim'

  return (
    <h1>Hello, {name}</h1>
  )
}

Vue 中有 v-for 指令,React 则需要自己来实现,下面的代码通过 map 来输出所有名字

const App: React.FC = () => {
  const nameArray = ['jim', 'ekko', 'lee']

  return (
    <ul>
      {nameArray.map(name =>
        <li key={name}>{name}</li>
      )}
    </ul>
  )
}

与 Vue 一样,你需要给每个元素一个 key 以优化渲染。同时,返回的必须是一个元素包裹起来的,如果你去掉最外层的 <ul> 那么代码将会报错。

再演示下 v-if 的实现

const App: React.FC = () => {
  const nameArray = ['jim', 'ekko', 'lee']
  const showName: boolean = false

  return (
    <ul>
      {nameArray.map(name =>
        showName && <li key={name}>{name}</li>
      )}
    </ul>
  )
}

我们使用 && 运算符,只有当前面的条件满足时才会继续渲染后面的内容。当 showName 为 true 时,我们才会去渲染出 <li> 。上面的代码其实是不优雅的,我们在 map 里使用 && 就和 Vue 中 v-for 里使用 v-if 是一样的,它会造成额外的计算,如果你还不知道为什么不推荐 v-for 和 v-if 同时使用,学习了 React 你或许就更加清楚了,下面我们来进行优化。

const App: React.FC = () => {
  const nameArray = ['jim', 'ekko', 'lee']
  const showName: boolean = false

  return (
    // 正常情况下,我们应该将 <ul> 也包裹起来,否则 showName 为 false 依然会渲染出一个 <ul>
    <ul>
      {showName && nameArray.map(name =>
        <li key={name}>{name}</li>
      )}
    </ul>
  )
}

我们将 showName 提到最前面,相当于将 v-for 放置到 v-if 子层,这样就会避免循环再判断了。关于 v-show 的实现其实就是 display: none 的切换,再后面讲到 style 的动态绑定就能实现了。

事件绑定

Vue 中,我们使用 @click 来监听一个点击事件,React 中使用 onClick 监听。其余的事件也是一样的道理。

const App: React.FC = () => {
  const nameArray = ['jim', 'ekko', 'lee']
  const showName: boolean = false

  return (
    <ul>
      {nameArray.map(name =>
        <li
          key={name}
          onClick={() => {
            console.log(<code>${name} is clicked</code>)
          }}
        >
          {name}
        </li>
      )}
    </ul>
  )
}

我们在点击事件中传入一个函数,为了方便我们传入了箭头函数,在项目中最好是定义一个函数去调用。同时 Vue 的事件是不可以传箭头函数的,因为 this 并不是指向的 vm,React 是可以的。

样式绑定

Style

React 使用对象来对 line-style 赋值,至于为什么,下面是官方文档的解释:The style attribute accepts a JavaScript object with camelCased properties rather than a CSS string. This is consistent with the DOM style JavaScript property, is more efficient, and prevents XSS security holes.

const App: React.FC = () => {
  return (
    // 两层大括号,第一层表示用 JS 解析,第二层才是对象
    // kebab-case 修改为 camel-case
    <h1 style={{ color: 'red', marginTop: '1px' }}>Hello, world!</h1>
  )
}

内联样式的写法可能很不符合我们常用的 CSS Style,不过也没有办法呀。你还可以在外部定义一个 style 对象再传入,那看起来像是 class 的做法

Class

React 使用 className 来定义 class,至于为什么不用 class 可能是因为和 class 类 关键词存在冲突吧

import React from 'react'
// 引入样式文件
import './style.css'

const App: React.FC = () => {
  return (
    <h1 className='my-class'>Hello, world!</h1>
  )
}

// 另一个文件,React 不支持像 Vue 一样的 style 写法
// style.css
.my-class {
    color: red;
    margin-top: 1px;
}

动态样式

你可以用 三目运算符,模板字符串等等实现动态样式

const App: React.FC = () => {
  const redColor: boolean = true

  return (
    <h1 style={{ color: redColor ? 'red' : 'inherent' }}>Hello, world!</h1>
  )
}

// 模拟 v-show
const App: React.FC = () => {
  const show: boolean = true

  return (
    // 使用模板字符串进行动态绑定 class,这里的 d-none 即 display: none 
    <h1 className={<code>${show ? '' : 'd-none'}</code>}>Hello, world!</h1>
  )
}

State

React 和 Vue 最大的对比就是双向绑定了,在 Vue 中,data 能够检测改变并更新 DOM,有人称之为 MVVM,Vue 并不是 MVVM 架构,其只是借鉴了 MVVM 的思想,具体可以到官方文档中查看。React 与 Vue 的 data 对应的是 State,直译过来也就是 状态 ,那么就能理所当然的理解了,状态是不会响应更新的,它仅仅是组件中的状态。但是我们一定会遇到更新状态并渲染的情况,这里我们使用 Hooks 的 useState 来实现。

const App: React.FC = () => {
  // 定义一个 name 的状态,name 是一个状态,setName 是一个函数,用于修改 name
  // 这里用到的是解构语法,简而言之就是 useState 返回了一个数组,我们使用 name 和 setName 接受
  const [name, setName] = useState<string>('ekko')

  // 错误的改变 name 的值,页面不会改变,而且你还会直接报错,因为 foo 是 const
  const changeNameError = (): void => {
    // name = 'ashe'
  }

  // 正确的改变 name,使用 setName
  const changeName = (): void => {
    setName('ashe')
  }

  return (
    <h1 onClick={changeName}>Hello, {name}</h1>
  )
}

Props

了解完 State,接下来我们再来看看怎么使用 Props 进行组件通信。React 的 Props 传递,其实是和 Vue 几乎一样的。下面演示一下一个最小完成体。

在父组件中给子组件传入 props,和 Vue 是一模一样的,就不累赘了。

// App.tsx
import React from 'react'
// 引入一个子组合
import AppTitle from './AppTitle'

const App: React.FC = () => {
  return (
    // title 为子组件中定义的 prop,你可以直接赋值或者使用变量赋值
    <AppTitle title='Props value' />
  )
}

export default App

子组件中变化还是有一点点,由于是 TypeScript 编写的,因此我们可以定义一个接口来声明 props,这样一来在子组件和父组件中就都有了提示和类型检查了。当然,也可以直接在 React.FC 中声明 props,不过这样子的可读性并不理想。和 Vue 直接使用 props 不同,React 子组件中使用 props 则需要加上 Props,这里的 Props 你也可以换成自己习惯的写法。个人更喜欢 React 的写法,这样能够清楚的知道哪些值是来自 props 的。

// AppTitle.tsx
import React from 'react'

// TypeScript 中我更加推荐定义一个接口来表明 Props
interface Props{
  title: string
}

// React.FC<Props> 表示该组件的 props 类型, 箭头函数中的 Props 则是父组件传入的 Props,当然你可以使用其他命名
const AppTitle: React.FC<Props> = (Props) => {
  return (
    // 我们使用箭头函数中的 Props 加上 . 访问相关的 props
    <h1>{Props.title}</h1>
  )
}

export default AppTitle

表单

其实是想先说说状态提升的,因此它和 Props 有一定的关联。不过发现状态提升往往是表单最尝遇到的,所以先来看看表单是怎么处理的。我们知道 Vue 中可以使用 v-model 来快速的建立一个双向绑定关系,React 并没有提供这类的便捷指令,但是你可以自己实现,相信在实现完后也会对 v-model 的实现由更深刻的理解。

和 Vue 一样,我们需要建立一个 state 来保存表单是输入。在表单改变时,调用 setState 来改变 state 的值。

import React, { useState } from 'react'

const AppInput: React.FC = () => {
  // useState Hook, 在 State 中提到过,后面会经常使用的
  const [valueState, setValueState] = useState<string>('')
  // 正确声明 event 类型往往能够让你的代码根据有可读性和代码提示
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    // 在触发 change event 的时候,我们在控制台打印值
    console.log(e.target.value)
    // 必要的:更新 state 值,否则 input 将一直是 valueState 的初始值
    setValueState(e.target.value)
  }

  return (
    // 绑定 value 值和 onChange 事件
    <input
      value={valueState}
      onChange={handleChange}
    />
  )
}

export default AppInput

可以说 React 更加偏向于原生的 JS ,Vue 往往将一些处理流程直接暴露成 API 来使用,这两种方式也是萝卜青菜各有所爱,但是你不能因为使用 Vue 连最基本的表单绑定都不会写哦。至于其他表单就举一反三或者面向 Google 编程吧。

状态提升 Lifting State Up

有的时候我们可能需要让子组件给父组件传值,无论是 Vue 还是 React 都是单向数据流的,因此由父向子使用 props 可能很简单,但是由子向夫会由一点点麻烦,下面我们来看看。我们在父组件中创建两个 input 子组件并让它们共同使用父组件的 state,已达到两个 input 互相更新,虽然这看上去很奇怪,但是我还没想好其他更加容易理解的例子。原来各自的 state 变成了父组件的 state,这就是状态提示的意思,这在分离组件时往往很常见。

import React from 'react'

// 我们将 value 和 handleChange 均交给父组件处理,在这个组件中只负责根据 Props 做出合适的渲染即可
interface Props{
  value: string
  handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
}

const AppInput: React.FC<Props> = (Props) => {
  return (
    <input
      value={Props.value}
      onChange={Props.handleChange}
    />
  )
}

export default AppInput

接下来是父组件

import React, { useState } from 'react'
import AppInput from './AppInput'

const App: React.FC = () => {
  // 没错我们只是把上面的表单例子的状态和处理函数粘贴过来了
  const [valueState, setValueState] = useState<string>('')
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    setValueState(e.target.value)
  }

  return (
    <div>
      {/* 给两个 input 一样的 valueState 和处理函数,这就实现了互相更新 */}
      <AppInput value={valueState} handleChange={handleChange} />
      <AppInput value={valueState} handleChange={handleChange} />
    </div>
  )
}

export default App

状态提示听起来可能很难,但是这样看是不是更加清晰了呢?如果还是不太清楚,你可以先认为就是子组件向父组件传值,我们通过父组件传来一个 handleChange 函数,子组件在处理时调用这个函数就可以“改变”父组件的 state 了。状态提示往往伴随着子组件的状态消失和父组件的新增状态,因为状态从子提示到了父中。在 Vue 中,就是 子组件的 data 变成 prop,但改变父组件的 data 往往是通过 $emit 实现,而不是在 props 中增加一个函数。

TypeScript + React

你可能已经通过上面的众多代码了解到了 TypeScript 如何配合 React 工作。React 加上 TypeScript 真香,如果 0202 年了还没有学过或了解 TypeScript 那赶紧去学吧。在 React中使用 TypeScript 我是看的 Youtube 上一个小哥的视频 大概也就十多二十分钟,讲解的还是很好哒。Note: 全英文,不过可以显示英文字幕。

React 思想

版权声明:转载注明出处

发表评论

电子邮件地址不会被公开。