React学习笔记-从Vue出发学React

1.前言

本文记录了React学习的全过程以及个人理解,由于作者对Vue比较熟悉,会在文章中将陌生的React知识点用Vue中的相应内容做比较,以加深理解。

2.环境搭建

全局安装react官方脚手架

npm install -g create-react-app // 安装失败提示没有权限则加上sudo命令

初始化一个用于学习的react项目

create-react-app react-learn

进入并启动项目

cd react-learn
npm start

3.主要概念

3.1 Hello World

学习任何一门语言都是从Hello World开始,React也不例外:
-将src目录下的所有文件删除,并新建一个index.js和一个index.css,
在这里插入图片描述
在index.js中写入如下代码

import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

ReactDOM.render(
    <h1>Hello, world</h1>,
    document.getElementById('root')
)

来看一下上面例子中的代码,ReactDOM对象通过render方法,传入两个参数,第一个参数为一个JSX表达式,第二个对象是挂载的目标节点,这样就达到了Hello World的效果
打开http://localhost:3000/,就可以看到Hello World的字样啦

3.2 JSX

什么是JSX

JSX,是一种 JavaScript 的语法扩展,在React中,使用JSX来描述用户界面。
在Vue中,我们使用template(模板)进行用户界面的实现,而JSX就是类似于template的一种语法,但需要主要的是,JSX是由JavaSript实现的,这也就是它和Vue的重要区别。
那么一个赋值为JSX的变量究竟是怎样一个东西呢,我们修改代码为如下格式:

const element = <h1>Hello, world</h1>
console.log(element)

ReactDOM.render(
    element,
    document.getElementById('root')
)

在控制台得到这样的输出:在这里插入图片描述
原来它是一个React封装好的对象,这里我们先不作深究。

在JSX中使用表达式

在Vue中,我们在template中可以插入js表达式,在React中,我们也可以在JSX中插入js表达式:

function formatName(user) {
    return user.firstName + ' ' + user.lastName
}    
const user = {
    firstName: 'Oliver',
    lastName: 'Shen'
}


const element = <h1>Hello,{formatName(user)}</h1>
console.log(element)

ReactDOM.render(
    element,
    document.getElementById('root')
)

不同的是,在Vue中只能插入data和methods中声明的变量和方法,而在React中可以插入任何位置声明的变量和方法。

贯彻着你中有我,我中有你的思想,由于JSX编译后会被转化为js对象,你可以将它赋值给任意的变量,甚至在方法中作为返回值:

function formatName(user) {
    return user.firstName + ' ' + user.lastName
}    
const user = {
    firstName: 'Oliver',
    lastName: 'Shen'
}
function getJSX() {
    return <h1>Hello,{formatName(user)}</h1>
}

ReactDOM.render(
    getJSX(),
    document.getElementById('root')
)

3.3 JSX属性

与Vue相同,你可以为JSX中的标签设置属性和值,值可以是字符串,也可以是一个变量。

const isEditable = true
const element = <div contentEditable="false"></div>
const element2 = <div contentEditable={isEditable}></div>

3.4 JSX嵌套

JSX自然也是可以嵌套使用的:

onst element = <div></div>
const element2 = <div>{element}</div>

ReactDOM.render(
    element2,
    document.getElementById('root')
)

3.5 另一种书写方式

由于Babel会把JSX转换成React.createElement()的方法调用,我们可以直接用这个方法书写React元素:

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

4.元素渲染

元素,也就是上面定义的一个个element,是构成React应用的最小单位。在主要概念中也讲到了RenderDOM.render()这个方法的作用,
这里要讲的是,如何更新元素渲染
在React中,所有元素都是不可变的,也就是在元素创建之后,无法改变其内容或属性,这里有一种通过setInterval()方法实现更新的方式:

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}

setInterval(tick, 1000);

此时,我们打开浏览器检查元素,会发现,只有不断改变的这个变量,在渲染过程中进行了更新。
也就是说:即便我们每秒都创建了一个描述整个UI树的新元素,React DOM 也只会更新渲染文本节点中发生变化的内容。

5.组件&Props

5.1 定义组件

在Vue中,一个.vue文件就可以称为一个组件,而在React中,一个组件可以是一个JavaScript函数:

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

ReactDOM.render(
    <Hello name="Oliver"></Hello>,
    document.getElementById('root')
)

上述代码中,定义了一个Hello函数,其实就是定义了一个Hello组件,这个组件接受一个props参数(类似Vue中的data),会读取传入的所有属性,并在组件中使用。需要注意的是:组件的首字母必须是大写的,否则无法区分html的原生标签,无法通过编译。

我们常用这种方式定义和使用组件:

class Hello extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>
    }
}

ReactDOM.render(
    <Hello name="Oliver"></Hello>,
    document.getElementById('root')
)
  • 上述两种方法有何区别呢?
    通过类实现的组件可以使用其私用的状态,也就是生命周期的钩子函数,而通过声明函数实现的组件缺不行,所以接下来的组件,都会以类的形式展现

5.2 提取组件

与Vue相同,在开发过程中,我们希望代码能够简介易读,同时能够提高代码的复用性,这也是组件的最初用意,我们试着在React中提取代码,封装成组件。

class Hello extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>
    }
}

class Info extends React.Component {
    render() {
        return <ul><li>Name: {this.props.name}</li><li>Age: {this.props.age}</li></ul>
    }
}
class Wrap extends React.Component {
    render() {
        return <section><Hello name={this.props.name}></Hello><Info name={this.props.name} age={this.props.age}></Info></section>
    }
}

ReactDOM.render(
    <Wrap name="Oliver" age="22"></Wrap>,
    document.getElementById('root')
)

上述代码提取了一个Hello组件和一个Info组件,并在Wrap组件中进行调用。

5.3 Props的只读性

顾名思义,在React中,自身的props是不可修改的:

class Hello extends React.Component {
    render() {
        this.props.name = 'Tom' // error
        return <h1>Hello, {this.props.name}</h1>
    }
}

那我们如何修改组件的状态呢,且听下回分解。

6.State&生命周期

6.1 State

在React中,我们使用state(状态)作为组件内可修改的数据,但它只能在以类声明的组件中使用,如:

class Clock extends React.Component {
  static defaultProps = {
     name: 'sls',
     age: 23
  };
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

其中,在constructor中声明该组件的state和props。注意,此处的props是通过super方法继承父组件的—这就类似于vue中从父组件获取data,在子组件中用props接收,并且可通过defaultProps关键字设置默认值。

那么问题来了,既然state是可修改的,那如何正确的使用state呢?

  • 使用setState()
this.setState({comment: 'Hello'});

setState会将参数中提供的对象浅合并到当前状态,这意味着该合并之后影响到state中你提供的属性,说白了也就是属性的修改不相互影响

  • 状态更新可能是异步的
    当setState修改到的属性依赖于其他属性时,使用以下方式:
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

看完了state的工作原理,这里就要引出react区别于vue,angular的一点了,那就是vue中数据的单向流动

这通常被称为自顶向下或单向数据流。 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。

6.2 生命周期

本章参考了掘金文章图解ES6中的React生命周期

React的生命周期与Vue相似,但又有一定的差异,我们对照着来看:

  • React
    达到
  • vue
    在这里插入图片描述

一、初始化阶段
1.设置组件的默认属性

static defaultProps = {
	name: 'sls',
	age: 23
};
// or
Counter.defaultProps = {
	name: 'sls',
	age: 23
};

defaultProps是react中使用类实现组件时保留的关键字,用于初始化组件的props,并且props作为组件的属性是不可修改的

2.设置组件的初始化状态

constructor() {
    super();
    this.state = {number: 0}
}

state也是 react使用类实现组件时保留的关键字,用于初始化组件的states,区别于props,states是可修改的,这也意味着,react中的states类似于vue中的data,常用于实际操作,如:父组件传值给子组件,父组件传递state给子组件,子组件用props接收。

上述两个过程类似于Vue中的create过程,初始化的组件的数据(data)

3.componentWillMount()

组件即将被渲染到页面之前触发,此时可以进行开启定时器、向服务器发送请求等操作,该钩子相当于vue中的beforeMount();

二、运行中阶段
1.componentWillReceiveProps()

组件接收到属性时触发,组件首次渲染时并不会触发

2.shouldComponentUpdate()

当组件接收到新属性,或者组件的状态发生改变时触发。组件首次渲染时并不会触发

shouldComponentUpdate(newProps, newState) {
    if (newProps.number < 5) return true;
    return false
}
//该钩子函数可以接收到两个参数,新的属性和状态,返回true/false来控制组件是否需要更新。

当一个父组件拥有多个子组件时,会出现以下这种影响性能的问题:父组件的state更新,导致所有子组件重新执行render方法,形成新的虚拟DOM,再通过比较新旧虚拟DOM判断组件是否需要重新渲染,这无疑造成了性能浪费。
因此我们可以在shouldComponentUpdate()中加入条件判断,筛去不必要的更新,从而优化性能

3.componentWillUpdate()

组件即将被更新时触发,相当于vue中的beforeUpdate(),

4.componentDidUpdate()

组件被更新完成后触发。页面中产生了新的DOM的元素,可以进行DOM操作,相当于vue中的updated()

三、销毁阶段
1.componentWillUnmount()

组件被销毁时触发。这里我们可以进行一些清理操作,例如清理定时器,取消Redux的订阅事件等等。相当于vue中的beforeDestroy()

7.事件处理

在React中,所有的事件命名采用驼峰式写法,并且传入的参数为js表达式:

<button onClick={handleClick}>Click</button>

在实际应用常中使用类声明组件的方式,则相应的事件处理如下:

class Alert extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: 'Oliver'
        }
        // this.handleClick = this.handleClick.bind(this)
    }
    handleClick() {
        console.log(this.state)
        alert('hahahahaha')
    }
    render() {
        return (
            // <button onClick={this.handleClick}>click</button> // 如果使用bind绑定this则不需要使用箭头函数
            <button onClick={() => this.handleClick()}>click</button>
        )
    }
}

由于React中,类的方法不会自动绑定this到当前组件上,因此需要通过bind为这个方法绑定this;或者也可以使用箭头函数,保持this指向当前组件。

  • 参数传递
    React中会自动向事件的回调函数返回一个event参数,你可以通过以下方式接收:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
// or
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button> // 该方法的e已通过隐式传递

当然你也可以传入自定义的参数,需要注意的是e必须在你传入的自定参数之后

8.条件渲染

条件渲染,也就是在通过不同条件控制组件是否渲染,渲染内容, 这一章更多的是讲在实际项目中的运用了,也就是Vue中的v-if指令了

function LoginButton(props) {
  return <button onClick={props.onClick}>Login</button>
}

function LogoutButton(props) {
  return <button onClick={props.onClick}>Logout</button>
}
class LoginArea extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      status: false
    }
  }
  login() {
    this.setState({
      status: true
    })
  }
  logout() {
    this.setState({
      status: false
    })
  }
  render() {
    let button = null
    if (this.state.status) {
      button = <LoginButton onClick={() => this.logout()} />
    } else {
      button = <LogoutButton onClick={() => this.login()} />
    }
    return <div>{button}</div>
  }
}

这里通过if判断status的状态,从而渲染不同的内容,这就类似于vue中的v-if指令。在render方法中可以也可以写一些业务代码,vue中的watchcomputed方法可以在这里有相似的实现

  • 阻止组件渲染
    有时候,你可能希望隐藏组件,即使它被其他组件渲染。让 render 方法返回 null 而不是它的渲染结果即可实现:
function Warning(props) {
  if (!props.isShow) {
    return null
  }
  return <div>warning!!!!!</div>
}
class WarnArea extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isShow: false
    }
  }
  showWarning() {
    this.setState(prevState => ({
      isShow: !prevState.isShow
    }))
  }
  render() {
    return (
      <div>
        <Warning isShow={this.state.isShow} />
        <button onClick={() => this.showWarning()}>
          {this.state.isShow ? "hidden" : "show"}
        </button>
      </div>
    )
  }
}

9.列表渲染

列表渲染也就是通过js循环的方式,生成一个列表,一次性渲染,这和Vue中的v-for指令是相似的:

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

通常,我们使用map方法来实现列表渲染, 这种声明式的代码可读性更好

与Vue相同,列表循环的列表项必须指定key属性:

function ListView(props) {
  return props.numbers.map(number => {
    return <li key={number}>{number * 2}</li>
  })
}

class List extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      numbers: [1, 2, 3, 4, 5]
    }
  }
  render() {
    return (
      <div>
        <ul>
          <ListView numbers={this.state.numbers} />
        </ul>
        <ul>
          <ListView numbers={this.state.numbers} />
        </ul> 
      </div>
    )
  }
}

需要注意的是,这里和vue有一定的区别:在Vue中,key值是组件内的唯一的,而在React中,key值是在兄弟元素间唯一的

10.受控组件&非受控组件

受控组件&非受控组件是指表单的可输入/可选择标签:input,textarea,select是否受用户控制。

受控组件

受控组件是指:用户的操作能够影响到组件的状态:

class MyForm extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      value: "haha"
    }
  }
  handleChange(event) {
    this.setState({
      value: event.target.value
    })
  }
  handleClick(event) {
    alert(this.state.value)
    event.preventDefault()
  }
  changeState(event) {
    event.preventDefault()
    this.setState({
      value: "heihei"
    })
  }
  render() {
    return (
      <form action="">
        <input
          type="text"
          value={this.state.value}
          onChange={e => this.handleChange(e)}
        />
        <input
          type="submit"
          value="Submit"
          onClick={e => this.handleClick(e)}
        />
        <button onClick={e => this.changeState(e)}>change</button>
      </form>
    )
  }
}

上述代码中能够,为input绑定了一个value,value的初始值即为state中的value属性。此时用户手动输入会造成value的变化,但这不会修改到state,因此,通过onChange方法,获取value并修改了state。这便是受控组件,并且state的value也和input实现了双向绑定。

非受控组件

相对的,非受控组件是指:用户的输入不会影响state中绑定的属性。

class MyForm extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      value: "haha"
    }
  }
  handleClick(event) {
    this.setState({
      value: this.input.value
    })
    alert(this.state.value)
    alert(this.input.value)
    event.preventDefault()
  }
  render() {
    return (
      <form action="">
        <input
          type="text"
          defaultValue={this.state.value}
          ref={input => (this.input = input)}
        />
        <input
          type="submit"
          value="Submit"
          onClick={e => this.handleClick(e)}
        />
      </form>
    )
  }
}

在非受控组件中,绑定属性需要使用defaultValue这个属性,当用户修改输入后,我们输出了state,发现其value属性并未发生变化,这也就实现了非受控组件。如果需要获取input变化后的值,可以通过ref中的回调参数来得到,回调返回的是一个input元素,通过其value属性可以获得相应的值

11.状态提升

由于React是单向数据流,因此无法像Vue那样做到父子组件通信。在React中,这种类似父子组件通信的场景是通过状态提升来实现的。

所谓状态提升,是指当遇到多个组件共用状态数据时,将状态数据提升到离它们最近的公共父组件当中,由父组件进行管理。

下面实现一个百分数与非百分数的相互转换组件:

  • 转换函数
    该函数根据传入的currenType判断为何种类型的数据,再将value进行相应转换
function change(value, currentType) {
  if (isNaN(value)) {
    return ""
  }
  if (currentType === 1) {
    return value * 100
  } else if (currentType === 2) {
    return value / 100
  }
}
  • 定义子组件
    定义子组件,并传入以下props:
    • type: 类型名称
    • value:对应输入框的值
    • onValueChange: 输入框值发生改变的回调
      子组件的状态提升到了父组件,也就不需要再子组件中对数据进行操作了
class MyInput extends React.Component {
  constructor(props) {
    super(props)
  }
  handleChange(e) {
    this.props.onValueChange(e.target.value)
  }
  render() {
    return (
      <fieldset>
        <legend>{this.props.type}</legend>
        <input
          type="text"
          value={this.props.value}
          onChange={e => this.handleChange(e)}
        />
      </fieldset>
    )
  }
}
  • 定义父组件
    父组件中的state由内部函数onValue1Change和onValue2Change控制,这两个函数是子组件的回调,当某个子组件修改数据时,执行相应回调
const TYPE_1 = "正常数据"
const TYPE_2 = "百分比"
// 父组件
class Wrap extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      currentType: 1,
      value: 0
    }
    this.onValue1Change = this.onValue1Change.bind(this)
    this.onValue2Change = this.onValue2Change.bind(this)
  }
  onValue1Change(value) {
    this.setState({
      currentType: 1,
      value
    })
  }
  onValue2Change(value) {
    this.setState({
      currentType: 2,
      value
    })
  }
  render() {
    const currentType = this.state.currentType
    const value = this.state.value
    console.log(currentType, value)
    const type_1Value = currentType === 1 ? value : change(value, currentType)
    const type_2Value = currentType === 2 ? value : change(value, currentType)
    return (
      <div>
        <MyInput
          type={TYPE_1}
          value={type_1Value}
          onValueChange={this.onValue1Change}
        />
        <MyInput
          type={TYPE_2}
          value={type_2Value}
          onValueChange={this.onValue2Change}
        />
      </div>
    )
  }
}

12.组合

组合这个概念在之前的学习中其实已经有涉及了,如:

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />

  );
}

在父组件中向子组件传入属性,以达到子组件的特殊实现,这就是组合的一种实现

接下来讲一讲组合的另一种实现:包含关系
包含关系类似于Vue中的插槽(slot),具体实现如下:

// 子组件
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
// 父组件
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

父组件可以引用子组件的标签内些内容,其全部内容会通过props的children属性传递到子组件,并在子组件调用的位置展示

13.结束语

至此,React的基本概念已经学习完了,说实话与Vue相比异曲同工,但又略显复杂,接下来要做的便是在项目中实战了。之后可能会写一篇React的进阶篇,尽情期待吧~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值