主页

一、React 介绍

1. React 起源与发展

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

2. React 与传统 MVC 的关系

轻量级的视图层库!A JavaScript library for building user interfaces

React 不是一个完整的 MVC 框架,最多可以认为是 MVC 中的 V(View),甚至 React 并不非常认可 MVC 开发模式;React 构建页面 UI 的库。可以简单地理解为,React 将界面分成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。

3. React 的特性

4. 虚拟DOM


二、create-react-app

全局安装 create-react-app

npm install -g create-react-app

创建一个项目

create-react-app 项目名称

如果不想全局安装,可以直接使用 npx

npx create-react-app 项目名称

这需要等待一段时间,这个过程实际上会安装三个东西

  1. react: react 的顶级库
  2. react-dom: 因为 react 有很多的运行环境,比如 app 端的 react-native, 我们要在 web 上运行就使用 react-dom
  3. react-scripts: 包含运行和打包 react 应用程序的所有脚本及配置

出现下面的界面,表示创建项目成功:

Success! Created 项目名称 at /dir/项目目录
Inside that directory, you can run several commands:

  npm start
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

    cd 项目名称
    npm start

Happy hacking!

根据上面的提示,通过 cd 项目名称 命令进入目录并运行 npm start 即可运行项目。
生成项目的目录结构如下:

├── README.md         使用方法的文档
├── node_modules      所有的依赖安装的目录
├── package-lock.json 锁定安装时的包的版本号,保证团队的依赖能保证一致。
├── package.json
├── public            静态公共目录
└── src               开发用的源代码目录

常见问题:
npm 安装失败

  • 切换为 npm 镜像为淘宝镜像
  • 使用 yarn,如果本来使用 yarn 还要失败,还得把 yarn 的源切换到国内
  • 如果还没有办法解决,请删除 node_modulespackage-lock.json 然后重新执行 npm install 命令
  • 再不能解决就删除 node_modulespackage-lock.json 的同时清除 npm 缓存 npm cache clean --force 之后再执行 npm install 命令

三、编写一个 react 应用程序

react 开发需要引入多个依赖文件:react.js、react-dom.js,分别又有开发版本和生产版本,create-react-app 里已经帮我们把这些东西都安装好了。把通过 CRA 创建的工程目录下的 src 目录清空,然后在里面重新创建一个 index.js. 写入以下代码:

// 从 react 的包当中引入了 React。只要你要写 React.js 组件就必须引入React, 因为 react 里有一种语法叫 JSX,要写JSX,就必须引入 React
import React from 'react'
// ReactDOM 可以帮助我们把 React 组件渲染到页面上去,没有其它的作用了。它是从 react-dom 中引入的,而不是从 react 引入。
import ReactDOM from 'react-dom'

// ReactDOM 里有一个 render 方法,功能就是把组件渲染并且构造 DOM 树,然后插入到页面上某个特定的元素上
ReactDOM.render(
  // 这里就比较奇怪了,它并不是一个字符串,看起来像是纯 HTML 代码写在 JavaScript 代码里面。这并不是合法的JavaScript 代码, “在 JavaScript 写的标签的” 语法叫 JSX-JavaScript XML。
  <h1>欢迎进入 React 的世界</h1>,
  // 渲染到哪里
  document.getElementById('root')
)

注意:
<React.StrictMode> 目前有助于:

  1. 识别不安全的生命周期
  2. 关于使用过时字符串 ref API 的警告
  3. 检测意外的副作用
  4. 检测过时的 context API

四、JSX 语法与组件

1. JSX 语法

JSX 将 HTML 语法直接加入到 JavaScript 代码中,再通过翻译器转换到纯 JavaScript 后由浏览器执行。
在实际开发中,JSX 在产品打包阶段都已经编译成纯 JavaScript,不会带来任何副作用,反而会让代码更加直观并易于维护。 编译过程由Babel 的 JSX 编译器实现。
https://reactjs.org/docs/hello-world.html

要明白 JSX 的原理,需要先明白如何用 JavaScript 对象来表现一个 DOM 元素的结构?
看下面的DOM结构

<div class='app' id='appRoot'>
  <h1 class='title'>欢迎进入 React 的世界</h1>
  <p>
    React.js 是一个帮助你构建页面 UI 的库
  </p>
</div>

上面这个 HTML 所有的信息我们都可以用 JavaScript 对象来表示:

{
  tag: 'div',
  attrs: { className: 'app', id: 'appRoot'},
  children: [
    {
      tag: 'h1',
      attrs: { className: 'title' },
      children: ['欢迎进入 React 的世界']
    },
    {
      tag: 'p',
      attrs: null,
      children: ['React.js 是一个构建页面 UI 的库']
    }
  ]
}

但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。

于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX结构转换成 JavaScript 的对象结构。

下面代码

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  render () {
    return (
      <div className='app' id='appRoot'>
      <h1 className='title'>欢迎进入 React 的世界</h1>
      <p>
        React.js 是一个构建页面 UI 的库
      </p>
      </div>
    )
  }
}

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

编译之后将得到这样的代码:

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  render () {
    return (
      React.createElement(
        "div",
        {
          className: 'app',
          id: 'appRoot'
        },
        React.createElement(
          "h1",
          { className: 'title' },
          "欢迎进入React的世界"
        ),
        React.createElement(
          "p",
          null,
          "React.js 是一个构建页面 UI 的库"
        )
      )
    )
  }
}

ReactDOM.render(
  React.createElement(App),
  document.getElementById('root')
)

React.createElement 会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等, 语法为:

React.createElement(
  type,
  [props],
  [...children]
)

所谓的 JSX 其实就是 JavaScript 对象,所以使用 React 和 JSX 的时候一定要经过编译的过程:

JSX — 使用 react 构造组件,bable 进行编译 —> JavaScript 对象 — ReactDOM.render() —> DOM 元素 —> 插入页面

class 组件

ES6的加入让JavaScript直接支持使用class来定义一个类,react创建组件的方式就是使用的类的继承,ES6 class 是目前官方推荐的使用方式,它使用了ES6标准语法来构建,看以下代码:

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component {
  render () {
    return (
      <h1>欢迎进入 React 的世界</h1>
    )
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
)
  • es6 class 组件其实就是一个构造器,每次使用组件都相当于在实例化组件,像这样:

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    class App extends React.Component {
    render () {
      return (
        <h1>欢迎进入 {this.props.name} 的世界</h1>
      )
    }
    }
    
    const app = new App({
    name: 'react'
    }).render()
    
    ReactDOM.render(
    app,
    document.getElementById('root')
    )

3. 函数式组件

import React from 'react'
import ReactDOM from 'react-dom'

const App = (props) => <h1>欢迎进入 React 的世界</h1>
// or
function App(){
  return <h1>欢迎进入 React 的世界</h1>
}

ReactDOM.render(
  // React组件的调用方式
  <App />,
  document.getElementById('root')
)
这样一个完整的函数式组件就定义好了。但要注意!注意!注意!组件名必须大写,否则报错

4. 组件的样式

行内样式

想给虚拟dom添加行内样式,需要使用表达式传入样式对象的方式来实现:

// 注意这里的两个括号,第一个表示我们在要 JSX 里插入 JS 了,第二个是对象的括号
<p style={{color:'red', fontSize:'14px'}}>Hello world</p>

行内样式需要写入一个样式对象,而这个样式对象的位置可以放在很多地方,例如 render 函数里、组
件原型上、外链 js 文件中

使用 class

React 推荐我们使用行内样式,因为 React 觉得每一个组件都是一个独立的整体
其实我们大多数情况下还是大量的在为元素添加类名,但是需要注意的是, class 需要写成
className (因为毕竟是在写类 js 代码,会收到 js 规则的现在,而 class 是关键字)

<p className="hello">Hello world</p>
注意:
class ==> className , for ==> htmlFor(label)

5. 事件处理

5.1、绑定事件

采用 on + 事件名 的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写 onclick, React 里的事件是驼峰 onClickReact 的事件并不是原生事件,而是合成事件

5.2、事件 handler 的写法

  • 直接在 render 里写行内的箭头函数(不推荐)
  • 在组件内使用箭头函数定义一个方法(推荐)
  • 直接在组件内定义一个非箭头函数的方法,然后在 render 里直接使用 onClick={this.handleClick.bind(this)}(不推荐)
  • 直接在组件内定义一个非箭头函数的方法,然后在 constructorbind(this)(推荐)

5.3、Event 对象

和普通浏览器一样,事件 handler 会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。不同的是 React 中的 event 对象并不是浏览器提供的,而是它自己内部所构建的。它同样具 event.stopPropagationevent.preventDefault 这种常用的方法

6. Ref的应用

6.1、给标签设置 ref="username"

通过这个获取 this.refs.username, ref 可以获取到应用的真实 dom

6.2、给组件设置 ref="username"

通过这个获取 this.refs.username, ref 可以获取到组件对象

6.3、新的写法

Ref = React.createRef()

<div ref={this.myRef}>hello</div>

// 访问 this.myRef.current

五、组件的数据挂载方式

1. 状态(state)

状态就是组件描述某种显示情况的数据,由组件自己设置和更改,也就是说由组件自己维护,使用状态的目的就是为了在不同的状态下使组件的显示不同(自己管理)

(1) 定义 state

第一种方式

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  state = {
    name: 'React',
    isLiked: false
  }

  render () {
    return (
      <div>
        <h1>欢迎来到{this.state.name}的世界</h1>
        <button>
          {
            this.state.isLiked ? '❤取消' : '❤收藏'
          }
        </button>
      </div>
    )
  }
}

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

另一种方式

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      name: 'React',
      isLiked: false
    }
  }

  render () {
    return (
      <div>
        <h1>欢迎来到{this.state.name}的世界</h1>
        <button>
          {
            this.state.isLiked ? '❤取消' : '❤收藏'
          }
        </button>
      </div>
    )
  }
}

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

this.state 是纯 js 对象,在 vue 中,data 属性是利用 Object.defineProperty 处理过的,更改 data 的数据的时候会触发数据的 gettersetter,但是 React 中没有做这样的处理,如果直接更改的话,react 是无法得知的,所以,需要使用特殊的更改状态的方法 setState

(2) setState

isLiked 存放在实例的 state 对象当中,组件的 render 函数内,会根据组件的 state 的中的 isLiked 不同显示 “取消” 或 “收藏” 内容。下面给 button 加上了点击的事件监听。

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      name: 'React',
      isLiked: false
    }
  }

  handleBtnClick = () => {
    this.setState({
      isLiked: !this.state.isLiked
    })
  }

  render () {
    return (
      <div>
        <h1>欢迎来到{this.state.name}的世界</h1>
        <button onClick={this.handleBtnClick}>
          {
            this.state.isLiked ? '❤取消' : '❤收藏'
          }
        </button>
      </div>
    )
  }
}

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

setState 有两个参数

第一个参数可以是对象,也可以是方法 return 一个对象,我们把这个参数叫做 updater

  • 参数是对象

    this.setState({
    isLiked: !this.state.isLiked
    })
  • 参数是方法

    this.setState((prevState, props) => {
    return {
      isLiked: !prevState.isLiked
    }
    })
    注意的是这个方法接收两个参数,第一个是上一次的 state, 第二个是 props

setState 是异步的

所以想要获取到最新的 state,没有办法获取,就有了第二个参数,这是一个可选的回调函数

this.setState((prevState, props) => {
  return {
    isLiked: !prevState.isLiked
  }
}, () => {
  console.log('回调里的',this.state.isLiked)
})

console.log('setState外部的',this.state.isLiked)

2. 属性(props)

props 是正常是外部传入的,组件内部也可以通过一些方式来初始化的设置,属性不能被组件自己更改,但是你可以通过父组件主动重新渲染的方式来传入新的 props

属性是描述性质、特点的,组件自己不能随意更改。

之前的组件代码里面有 props 的简单使用,总的来说,在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为组件 props 对象的键值。通过箭头函数创建的组件,需要通过函数的参数来接收 props :

  1. 在组件上通过 key=value 写属性,通过 this.props 获取属性,这样组件的可复用性提高了。
  2. 注意在传参数时候,如果写成 isShow="true" 那么这是一个字符串 如果写成 isShow={true} 这个是布尔值
  3. {...对象} 展开赋值
  4. 默认属性值

    *.defaultProps = {
    }
      static defaultProps = {
     myname:"默认的myname",
     myshow:true
    }
  5. prop-types 属性验证

    import propTypes from "prop-types";
    *.propTypes={
      name:propTypes.string,
      age:propTypes.number
    }
    
    static propTypes={
      myname:propTypes.string,
      myshow:propTypes.bool
    }

3. 属性 vs 状态

相似点:都是纯 js 对象,都会触发 render 更新,都具有确定性(状态/属性相同,结果相同)

不同点:

  1. 属性能从父组件获取,状态不能
  2. 属性可以由父组件修改,状态不能
  3. 属性能在内部设置默认值,状态也可以,设置方式不一样
  4. 属性不在组件内部修改,状态要在组件内部修改
  5. 属性能设置子组件初始值,状态不可以
  6. 属性可以修改子组件的值,状态不可以
state 的主要作用是用于组件保存、控制、修改自己的可变状态。 state 在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。 state 中状态可以通过 this.setState 方法进行更新, setState 会导致组件的重新渲染。
props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props ,否则组件的 props 永远保持不变。
没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。

4. 渲染数据

  • 条件渲染

    {
    condition ? '渲染列表的代码' : '空空如也'
    }
  • 列表渲染

    // 数据
    const people = [{
    id: 1,
    name: 'Leo',
    age: 35
    }, {
    id: 2,
    name: 'XiaoMing',
    age: 16
    }]
    
    // 渲染列表
    {
    people.map(person => {
      return (
        <dl key={person.id}>
          <dt>{person.name}</dt>
          <dd>age: {person.age}</dd>
        </dl>
      )
    })
    }

React 的高效依赖于所谓的 Virtual-DOM,尽量不碰 DOM。对于列表元素来说会有一个问题:元素可能会在一个列表中改变位置。要实现这个操作,只需要交换一下 DOM 位置就行了,但是 React 并不知道其实我们只是改变了元素的位置,所以它会重新渲染后面两个元素(再执行 Virtual-DOM ),这样会大大增加 DOM 操作。但如果给每个元素加上唯一的标识,React 就可以知道这两个元素只是交换了位置,这个标识就是 key ,这个 key 必须是每个元素唯一的标识。

dangerouslySetInnerHTML

对于富文本创建的内容,后台拿到的数据是这样的:content = "<p>React.js是一个构建UI的库</p>"

处于安全的原因,React 当中所有表达式的内容会被转义,如果直接输入,标签会被当成文本。这时候就需要使用 dangerouslySetHTML 属性,它允许我们动态设置 innerHTML
import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class App extends Component {
  constructor() {
    super()
    this.state = {
      content : "<p>React.js是一个构建UI的库</p>"
    }
  }

  render () {
    return (
      <div
      // 注意这里是两个下下划线 __html
      dangerouslySetInnerHTML={{__html: this.state.content}}
      />
    )
  }
}

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

六、表单中的受控组件与非受控组件

1. 非受控组件

React 要编写一个非受控组件,可以 使用 ref 来从 DOM 节点中获取表单数据,就是非受控组件。
例如,下面的代码使用非受控组件接受一个表单的值:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
      this.handleSubmit = this.handleSubmit.bind(this);
      this.input = React.createRef();
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。

默认值

在 React 渲染生命周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个 defaultValue 属性,而不是 value。

render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input defaultValue="Bob" type="text" ref={this.input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}
同样,<input type="checkbox"><input type="radio"> 支持 defaultChecked<select><textarea> 支持 defaultValue

2. 受控组件

在 HTML 中,表单元素(如 <input><textarea><select>)通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState() 来更新。

我们可以把两者联合起来使用,使 React 的 state 成为 “唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作,被 React 以这种方式控制取值的表单输入元素就叫做 “受控组件”。

例如,如果我们想让前一个示例再提交时打印出名称,可以将表单写为受控组件:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({
      value: event.target.value
    });
  }

  handleSubmit(event) {
    alert('提交的名字:' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type=""submit value="提交" />
      <form/>
    );
  }
}

由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。

对于受控组件来说,输入的值始终由 React 的 state 驱动。你也可以将 value 传递给其他 UI 元素,或者通过其他事件处理函数重置,但这意味着你需要编写更多的代码。

注意: 另一种说法(广义范围的说法),React 组件的数据渲染是否被调用者传递的 props 完全控制,控制则为受控组件,否则非受控组件。

七、组件通信的方式

1. 父子组件通信方式

(1) 传递数据(父传子)与传递方法(子传父)

(2) ref 标记 (父组件拿到子组件的引用,从而调用子组件的方法)

在父组件中清除子组件的 input 输入框的 value 值。this.refs.form.reset()

2. 非父子组件通信方式

(1) 状态提升(中间人模式)

React 中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props 分发给子组件。

(2) 发布订阅模式实现

// 调度中心
const bus = {
  list: [],

  // 订阅
  subscribe (callback) {
    this.list.push(callback)
  },

  // 发布
  publish (text) {
    // 遍历所有的 list,将回调函数执行
    this.list.forEach((callback) => {
      callback && callback(text)
    })
  }
}

// 订阅者
bus.subscribe((val) => {
  console.log(111, val)
})
// 订阅者
bus.subscribe((val) => {
  console.log(222, val)
})

// 发布者
setTimeout(() => {
  bus.publish('publish-1')
}, 0)
setTimeout(() => {
  bus.publish('publish-2')
}, 1000)

(3) context 状态树传参

// 1. 先定义全局 context 对象
import React from 'react'
const GlobalContext = React.createContext()
export default GlobalContext
// 2. 根组件引入 GlobalContext,并使用 GlobalContext.Provider(生产者)

// 重新包装根组件 class App {}

<GlobalContext.Provider
  value={{
    name:"Joe",
    age:100,
    content:this.state.content,
    show:this.show.bind(this),
    hide:this.hide.bind(this)
  }}
>

<之前的根组件></之前的根组件>
</GlobalContext.Provider>
// 3. 任意组件引入 GlobalContext 并调用 context,使用 GlobalContext.Consumer(消费者)
<GlobalContext.Consumer>
{
  context => {
    this.myshow = context.show; // 可以在当前组件任意函数触发
    this.myhide = context.hide; // 可以在当前组件任意函数触发
    return (
      <div>
        {context.name}-{context.age}-{context.content}
      </div>
    )
  }
}
</GlobalContext.Consumer>

注意:GlobalContext.Consumer 内必须是回调函数,通过 context 方法改变根组件状态

context优缺点:
优点:跨组件访问数据
缺点:react 组件树种某个上级组件 shouldComponetUpdate 返回 false,当 context 更新时,不会引起下级组件更新

React

版权属于:Joe
作品采用:本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
3

目录

来自 《React17-全家桶(1)》
评论

Joe

博主很懒,啥都没有
175 文章数
14 评论量
3 分类数
178 页面数
已在风雨中度过 1年61天12小时13分