React 学习第一天--react回顾

851 阅读17分钟

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

现在我们开始学习React,如果你是开始学React可以跟着学习,有疑问在评论区讨论;如果是已经学React我们当做回顾,后面一步一步加深,慢慢系统化学习!!!

下面对react基本介绍:

  • 什么是React;
  • JSX是什么,怎么使用。
  • React组件是什么,数据是怎么流转的(单向数据流原则,props,state, context),类组件的生命周期
  • React的表单
  • React-route相关

1. what is React?

A JavaScript library for building user interfaces

  • React 是一个用于构建用户界面的javaScript库,它只负责应用的视图层

    以前我们构建用户界面的时候使用的是html + css + JavaScript,但是使用react以后,我们可以构建用户界面完全使用JavaSript了,React 有个说法加 all in js,一切都是用js。结构,样式,逻辑都是用javascript。也就是说react就是构建用户界面的js库,也就是只负责用户界面的视图层。

  • 帮助开发人员快速构建且交互复杂的web应用程序,这句话怎么理解呢? 在React当中,拥有virtual DOM,diff算法,fiber算法,批量更新等等特性,使得使用react做出来的web 应用程序他的速度算是较快的

  • React 使用组件的方式构建用户界面。 在react当中,没有把构建用户界面,和构建界面的逻辑区分开来,所以使用react可以较轻松的构建强交互式web应用程序

  • 如果开发一个web App应用还需要学 react-router前端路由管理器,用于控制前端页面的跳转。还需要使用Redux等数据状态管理的工具,才能开发一个完整的web app应用

  • React使用JSX语法来描述用户界面

2. 那什么是JSX?

  • 在React中使用JSX语法描述用户界面,它是一种JavaScript语法扩展
// 这是一段jsx
<h1> Hello, {formatName(user)}!<h1>
  • 上面代码是一个jsx不可以直接给浏览器使用, 在React代码执行之前,需要使用Babel将JSX语法转换为标准的JavaScript API

image.png

babel会将上面代码转化成下面这个样子,可以直接被浏览器识别且可以运行的js代码

image.png

可以在官网查看

  • JSX 语法就是一种语法糖,目的让开发人员使用更加舒服的代码构建用户界面

2.1 在JSX中可以使用表达式

在大括号{}里面就可以使用javascript表达式

<div>
    Hello {this.props.name}
</div>

JSX本身也是一种表达式,将他赋值给变量,活当做参数传入,作为返回值都可以

2.2 属性

如果属性值为字符串类型,需要加引号,如果属性名称多个单词组成推荐使用驼峰式命名法

const element = <div greeting="hello"></div>

如果属性值为JavaScript表达式,属性值外面加大括号

const element = <img sec={user.avataUrl}/>

2.3 JSX单标记必须闭合

如果JSX是单标记,必须闭合,否则报错

// 值是js表达式
const element = <img src={user.avararUrl}  />
// 值是字符串
const element1 = <input type="text">

2.4 className

为JSX标记添加类名需要使用className,而不是class,因为class是js关键字

const element = <img className="rounded">

2.5 JSX 自动展开数组

JSX会自动展开数组

    const ary = [<p>哈哈</p>, <p> 呵呵</P>, <p>嘿嘿</p>]
    const element = {
        <div>{ary}</div>
    }
    
// 解析后
/*
<div>
  <p>哈哈</P>
  <p>呵呵</P>
  <p>嘿嘿</P>
</div>

2.6 JSX可以使用三元运算

如果js表达式返回的是 布尔值或者null,用户界面中就啥都不显示

{boolean ? <div>Hello React</div>:null}
{boolean && <div>Hello React</div>}

2.7 循环

如果在vue中可以使用v-for指令来操作,在React中不存在指令这个东西,那在React中如何使用循环呢?我们可以JSX中直接使用js的方法即可

const persons = [{
    id: 1,
    name: '张三'
},{
    id: 2,
    name: '李强'
},{
    id: 3,
    name: '王子'
}]
<ul>
   {
       persons.map(person => <li key={person.id}>{person.name})
   }
</ul>

2.8 事件

{/* 第一个参数即是事件对象 不需传递 */}
<button onClick={this.eventHandler}>按钮</button>
{/* 需要传递事件对象 */}
<button onClick={e=>this.eventHandler('arg',e)}>按钮</button>
{/* 最后一个参数即是事件对象 不需传递 */}
<button onClick={this.eventHandler.bind(null, 'arg')}>按钮</button>
constructor () {
  this.eventHandler = this.eventHandler.bind(this)
}
eventHandler () {}
<button onClick={this.eventHandler}>按钮</button>

2.9 样式

在react当中使用组件方式开发用户界面的,我们给这个组件添加样式的时候呢,实际上在大多数情况下我们都希望,写的样式只服务当前的组件本身,不让样式泄露到组件以外去。

  • 第一种为组件添加样式的方式就是使用行内样式

    • 使用行内样式时,如果属性key明是多个单词组成,那么就使用驼峰命名
    • 使用属性值是数值类型,那么可以不添加单位,默认是px
class App extent Component {
    render() {
        const style = {width: 200, height: 200, backgroundColor: 'red'};
        return <div style={style}></div>
    }
}

  • 第二种: 外链样式 除了使用js编写样式,我们也可以使用css,如果希望只用于对应的组件的话,样式文件名字是有相应约定的 如果组件名是Button 样式文件名必须是Button.module.css,后缀无所谓
// Button.js
import styles from './Button.module.css'
class Button extends Component {
    render() {
        return <button className={styles.error}>Error Button</button>
    }
}
  • 第三种全局样式 当做模块引入即可
import './styles.css'

2.10 属性

    1. createRef 获取元素或者组件实例的对象 这个input绑定了this.inputRef,在按钮点击的时候this.inputRef的current就是input元素标签的实例对象
class Input extends Component {
    constructor() {
        super()
        this.inputRef = React.createRef()
    }
    render() {
        return {
            <div>
                <input type="text" ref={this.inputRef)}>
                <button onClick={() => console.log(this.inputRef.current)}>button</button>
            </div>
        }
    }
}
  • 函数参数 元素的ref值也可以是一个函数,参数是当前组件或者标签的DOM实例对象
class Input extends Component {
  render() {
    return (
      <div>
        <input type="text" ref={input => (this.input = input)} />
        <button onClick={() => console.log(this.input)}>button</button>
      </div>
    )
  }
}

3. 组件

3.1 什么是组件

上面也说了React是基于组件的方式进行用户界面开发的,组件可以理解为对页面中某一块区域按照一定规则进行封装

image.png

3.2 创建组件

在React当中有两种类型组件一种是类组件一种是函数组件

3.2.1创建类组件

创建组件一定要继承React里面Component对象,在类组件当中一定要有一个render方法,render返回要呈现在页面当中的内容,出了要引用Component类,还需要引入React,但是我们看下面代码也没使用到React类为什么要引用呢,看上面JSX使用Babel转化后的样子就明白了,JSX会转化出一个React.createElement方法,这时候会使用到React类

import React, { Component } from 'react'
class App extends Component {
    render () {
        return () {
            return <div>Hello, 我是类组件</div>
        }
    }
}

3.2.2 创建函数组件

  • 组件名首字母必须大写,用以区分组件和普通标签
  • jsx 语法外层必须有一个根元素
const Person = () => {
    return <div>Hello, 我是函数型组件</div>
}

3.3 组件props

3.31 使用props传递数据

在调用组件时可以向组件内部传递数据,在组件中可以通过props对象获取外部传递进来的数据

<Person name="乔治" age="20"/>
<Person name="玛丽" age="10"/>

我们调用类组件的时候,使用组件标签调用,使用标签属性传入props,类组件使用this.props进行接收传入的值

// 类组件
class Person extends Component {
  render() {
    return (
      <div>
        <h3>姓名:{this.props.name}</h3>
        <h4>年龄:{this.props.age}</h4>
      </div>
    );
  }
}

我们调用函数组件也是一样,使用组件标签调用,但是函数组件接收一个参数是props,使用props接收传入的值

// 函数组件
const Person = props => {
  return (
    <div>
      <h3>姓名:{props.name}</h3>
      <h4>年龄:{props.age}</h4>
    </div>
  );
}

注意:

  1. props 对象中存储的数据是只读的,不能再组件内部被修改。
  2. 当props数据源中的数据被修改后,组件中的接受到的props数据会被同步更新。(数据驱动DOM)

3.3.2 设置props 默认值

类组件中使用静态属性 defaultProps去设置

class App extends Component {
    static defaultProps = {}
}

函数使用这个函数添加一个属性defaultProps去设置

function ThemedButton(props) {
}
ThemedButton.defaultProps = {
  theme: "secondary",
  label: "Button Text"
};

3.3.3 组件children

通过props.children属性可以获取到在调用组件时填充到组件标签内部的内容

<Person>组件内部的内容</Person>

使用props.children既可以获取组件里面的内容

const Person = (props) => {
    return (
    	<div>{props.children}</div>
    );
}

3.3.4 单向数据流原则

  1. 在React中,关于数据流动有一条原则,就是单向数据流动,自顶向下,从父组件到子组件。是不允许数据从子组件往父组件传递的
  2. 单项数据流特性要求我们共享数据要放置在上层组件中
    • 如果在两个子组件都需要同一个数据的时候,我们要将该数据定义在父级组件上,然后一层一层往下传递
  3. 子组件通过调用父组件传递过来的方法更改数据
    • 我们知道props是只读的,如果就是想通过子组件按钮点击更改props怎么办呢,应该在顶层组件中定义一个更改props的方法,通过props一次传递给对应的子组件,当子组件点击时调用该方法完成props的变化
  4. 当数据发生更改时,react会重新渲染组件树
  5. 单向数据流使用组件之间的数据流动变得可预测,使得定位程序变得简单,因为这样数据的流动就会变得有迹可循

image.png

3.4 类组件状态 state

3.4.1 定义组件状态

类组件除了能够从外部(props)接收状态数据以外还可以拥有自己的状态(state),此状态在组件内部可以被更新,状态更新DOM就会更新。

组件内部状态数据被存储在组件类中的state属性中,state属性值为对象类型,属性名称固定不可更改。

class App extends Component {
  constructor () {
    super()
    this.state = {
      person: { name: '张三', age: 20 },
    }
  }
  render () {
    return (
      <div>
        {this.state.person.name}
        {this.state.person.age}
      </div>
    );
  }
}

3.4.2 更改组件状态

state 状态对象中的数据不可直接更改,如果直接更改DOM不会更新,要更改state状态数据需要使用setState方法.

class App extends Component {
  constructor () {
    this.state = {
      person: { name: '张三', age: 20 },
    }
    this.changePerson = this.changePerson.bind(this)
  }
	changePerson () {
    this.setState({
      person: {
        name: '李四',
        age: 15
      }
    })
  }
  render() {
    return (
      <div>
        {this.state.person.name}
        {this.state.person.age}
        <button onClick={this.changePerson}>按钮</button>
      </div>
    );
  }
}

3.4.3 双向数据绑定

双向数据绑定是指,组件类中更新了状态,DOM状态同步更新,DOM更改了状态,组件类中同步更新。组件 <=> 视图

要实现双向数据绑定需要用到表单元素和state状态对象.

class App extends Component {
  constructor () {
    this.state = {
      name: "张三"
    }
    this.nameChanged = this.nameChanged.bind(this)
  }
  nameChanged (event) {
    this.setState({name: event.target.value});
  }
  render() {
    return (
      <div>
        <div>{this.state.name}</div>
        <Person name={this.state.name} changed={this.nameChanged}/>
      </div>
    )
  }
}

在表单中使用value绑定person.name属性,使用表单input组件onChange时间绑定changed方法,这样就可以表单值发生变化,person.name通过setState得到更新,导致视图更新

const Person = props => {
	return <input type="text" value={props.name} onChange={props.changed}/>;
}

3.5 类组件生命周期函数

什么是生命周期函数呢?当框架程序运行到某一时刻会去自动调用的函数,就是生命周期函数。 框架使用者可以就可以利用生命周期函数,在框架程序运行到某一时刻的时候,使用生命周期函数去做一些在这个时刻他想做的事情。

生命周期函数分为三个阶段:

  • 组件挂载阶段

    • 当一个组件被创建的时候,这时候会执行constructor函数,这里面会进行初始化(State)状态对象,去改变this指向
    • 当constructor执行完之后,就会去执行getDerivedStateFromProps生命周期函数,如果当前子组件的状态如果取决于父组件的状态,getDerivedStateFromProps就会去调用这个周期函数,这个周期函数接收两个参数,第一个接收父组件的state,第二个参数是当前组件的state。我们可以通过这两个参来决定要不要更新当前组件的state,如果不需要更新当前组件state,那么就让这个周期函数返回一个null,如果要更新就返回一个全新的state状态对象,切记不要什么都不返回,这是不被允许的
    • 当getDerivedStateFromProps执行完之后,接下来会执行render方法,这里会去挂载组件实例对象
    • 当render执行完之后就会去执行componentDidMount周期函数,也就预示着当前组件对象已经挂载完成了

    注意点: 挂载阶段, 不要在constructor当中去引起一些副作用,比如向服务端请求获取一些数据。这样的类似操作最好放在componentDidMount中进行

  • 组件数据更新阶段

    • 当组件当中的数据发生更新的时候,会去执行getDerivedStateFromProps周期函数,看一下组件的状态有没有必要发生更新
    • 在接下来就会去调用shouldComponentUpdate周期函数,这个生命周期函数当中要返回一个true或者false,如果返回true就会继续执行接下来的生命周期函数,如果返回false那么就停止更新组件
    • 假设现在shouldComponentUpdate周期函数返回true,接着下就会执行render周期函数,重新去渲染更新组件
    • render方法执行完以后,就会去执行getSnapshotBeforeUpdate周期函数,这个周期函数用的相对较少,这个周期函数用于执行某种逻辑或者计算,返回值可以在componentDidUpdate方法中的第三个参数中获取,就是说在组件更新之后可以拿这个值再去做其他事情 在组件完成更新之前需要做某种逻辑或者运算,就需要用到快照
        componentDidUpdate(preProps, preState, snapshot) {}
        getSnapshotBeforeUpdate(preProps, prevState) {
        return 'snapshot'
        }
    

    componentDidUpdate, getSnapshotBeforeUpdate配合使用

  • 组件卸载阶段

    • 这个阶段很简单,会执行componentWillUnmount周期函数。表示这个组件即将被卸载,在这个周期函数中做一些清理操作,清理组件的事件,清理一些refs绑定的dom对象等

image.png

3.6 Context

这个Context作用是什么呢?上面我们已经说了,props属性传递值只能通过父子组件从上到下一层一层传递,有的时候我们的组件层级太深,如果我们一次去传递是比较麻烦且难以维护的,这个时候我们就可以使用Context跨层级传递数据,我们来看下面一张图:

image.png

当根组件有一个数据叫做username,这个数据要被A组件使用,要被D组件使用,要被F组件使用,这是后应该怎么办呢,使用传统的方法props的话,通过props传递给A 通过props传递给B在props传递给D,通过第一层C的props传递给E,在通过E的Props传递给F....... 你也可以感知到这个过程后太复杂了,而且B,C,D也不用这个props,但是参与了这个数据的传递,这样不是太好。这时我们可以使用Context跨级传递,A ,D,F直接拿到根组件username这条数据

// 创建一个useContext文件

// userContext.js
import React from "react"

const userContext = React.createContext()
const UserProvider = userContext.Provider
const UserConsumer = userContext.Comsumer

export {UserProvider, UserConsumer}

createContext函数还可以传递默认值,如果UserProvider 的value没有传递值时,则使用默认值

// App.js
import { UserProvider } from "./userContext"
class App extends Component {
  render() {
    return (
      <UserProvider value="Hello React Context">
        <A />
      </UserProvider>
    )
  }
}
// C.js
import { UserConsumer } from "./userContext"

export class C extends Component {
  render() {
    return (
      <div>
        <UserConsumer>
          {username => {
            return <div>{username}</div>
          }}
        </UserConsumer>
      </div>
    )
  }
}

表单

在react当中呢,有两种类型的表单,受控表单和非受控表单

4.1 受控表单

所谓受控表单,指的是表单控件当中绑定的值都用state来管理,state对象中存储的值和表单控件中的值属于同步状态。

思考: 这种同步状态是如何实现的呢?

其实通过我们上面讲的通过数据双向绑定的机制实现的。组件的数据一定是绑定到控件value属性的,表单组件就会显示组件状态state.name的值,如果组件state.name和value的值保持同步更新,还需要给input添加onChange属性,特就是说当表单事件发生改变的时候还要去调用setState方法更新组件的state同步数据。这样就可以做到表单控件中的数据和组件state中的数据做一个同步。这样的话当表单做一个提交行为的时候使用this.state拿到表单当中存储的数据了

class App extends Component {
    constructor () {
         this.state = {username: ""}
         this.nameChanged = this.nameChanged.bind(this)
    }

    nameChanged(e) {
        this.setState({usename: e.target.value})
    }
    
    render() {
        return {
            <form>
                <p>{this.state.username}</p>
                <input type="text" value={this.state.username} onChange={this.nameChanged}/>
            </form>
        }
    }
}

4.2 非受控表单

非受控表单就是普通的表单,表单元素的值由DOM元素本身管理。通过ref获取表单组件对象实例,通过表单控件实例获取表单控件的值

class App extends Component {
  constructor () {
    this.onSubmit = this.onSubmit.bind(this)
  }
  onSubmit(e) {
    console.log(this.username.value)
    e.preventDefault();
  }
  render(
    <form onSubmit={this.onSubmit}>
      <input type="text" ref={username => this.username = username}/>
    </form>
  )
}

5. 路由

通过路由可以指定组件和URL对应关系,访问不同的url地址显示不同的组件

下载: npm install react-router-dom

5.1.1 路由的基本使用

  • BrowserRouter组件: 代表浏览器路由组件,应该把这个组件放到我们应用的最外层组件当中,这是规定
  • Route用来设置路由规则,匹配路由规则的
  • Link组件用来设置链接用的
// App.js
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
function Index() {
	return <div>首页</div>;
}
function News() {
	return <div>新闻</div>;
}
function App() {
  return (
    <Router>
      <div>
        <Link to="/index">首页</Link>
        <Link to="/news">新闻</Link>
      </div>
      <div>
      // 使用Route组件绑定路径和组件的关系
        <Route path="/index" component={Index}/>
        <Route path="/news" component={News}/>
      </div>
    </Router>
  );
}

5.1.2 路由嵌套 如果在应用中需要使用以及二级链接三级链接四级链接....

我们就需要使用路由嵌套,加入从新闻路由下又有公司新闻和行业新闻,这时候就需要二级嵌套路由了,那该如何设置呢?看代码:

function News(props) {
  return (
    <div>
      <div>
        <Link to={`${props.match.url}/company`}>公司新闻</Link>
        <Link to={`${props.match.url}/industry`}>行业新闻</Link>
      </div>
      <div>
        <Route path={`${props.match.path}/company`} component={CompanyNews} />
        <Route path={`${props.match.path}/industry`} component={IndustryNews}/>  
      </div>	
    </div>
  );
}

function CompanyNews() {
	return <div>公司新闻</div>
}
function IndustryNews() {
	return <div>行业新闻</div>
}

配置和之前一级差不多,关键在于怎么去指定对应的地址呢?我们通过props.match.url来获取上一级Link组件to属性的值,在加上当前路由自己的地址完成指定组件的地址。route组件中使用props.match.path获取上级组件的route的path值

5.1.3 路由传参

需求: 关于新闻呢有一个列表页,有一个详情页。在列表页当中有一个连接,我们点击连接的时候可以进入到对应的详情页面,当用户点击列表的时候我们要把该详情的id传递到详情页组件当中。

首先看在列表组件当中有个状态list:是一个数组,存着两个对象,每个对象当中都有id,和title两个属性

this.state = {
    list: [{
        id: 1title: '新闻'
    }, {
        id: 2,
        title: '新闻'
    }]
}

在render方法中通过map方法循环list,在循环中通过link组件创建链接/detail?id=${item.id},在链接后面传参为新闻id,链接内容就是item.title

render() {
    return (
      <div>
        <div>新闻列表组件</div>
        <ul>
          this.state.list.map((item, index) => {
            return (
              <li key={index}>
                <Link to={`/detail?id=${item.id}`}>{item.title}</Link>
              </li>
            );
          })
        </ul>
      </div>
    );
  }
}

接下来我们怎样获取,在详情页怎么获取新闻id呢? 我们可以通过this.props.location.search获取传递过来的参数,但是拿到的是带有?=字符串,比较繁杂,我们可以使用url来格式化该值

class Detail extends Component {
  constructor(props) {
    super(props);
  }
	const { query } = url.parse(this.props.location.search, true);
	console.log(query); // {id: 1}
  render() {
    return <div>新闻详情</div>
  }
}

5.1.4 路由重定向

import {Redirect} from 'react-router-dom'

class Login extends Component {
    render() {
        if(this.state.isLogin) {
            return <Redirect to="/">
        }
    }
}

6. 总结

react学习第一天: 希望对react有一个大概的认识:

  • 以数据驱动DOM UI = Render(Data),
  • 使用JSX在js中更好的书写React组件
  • 了解react组件有函数组件和类组件,知道组件依靠props自上而下一层一层传递数据,也可以使用context跨层级传递数据,ref获取组件实例。类组件中还有自己的状态state;类组件的生命周期分挂载,更新,卸载三个阶段,每个阶段执行不同的钩子函数。
  • 在react框架下有受控表单和非受控表单
  • 还使用react路由,知道了路径和组件匹配关系,嵌套路由的配置,和路由重定向

下期学习React数据流方案,敬请期待!!!

参考教程:拉钩教育