Javascript

React从0到1系列第二章——React的使用方式和基础API介绍

本文主要是介绍React从0到1系列第二章——React的使用方式和基础API介绍,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、React简介

因为之前做了挺久的Vue开发。现在开始学习React也是并不难的。首先要对React的整体脉络要有个大概的了解。(注:这里只做学习的记录和总结,有些可能会遗漏,后期有了新的感悟也会慢慢完善,一些基础的语法的使用还是要看官方网站为主)。

从上图可以得知React分为三大体系:

  1. React.js
  2. ReactNative
  3. ReactVR

学习的顺序也是React.js->ReactNative->ReactVR循序渐进的。

React的基础大体包括下面这些概念:

  1. 组件
  2. JSX
  3. Virtual DOM
  4. Data Flow

React.js不是一个框架,它只是一个库。它只提供 UI (view)层面的解决方案。在实际的项目当中,它并不能解决我们所有的问题,需要结合其它的库,例如 ReduxReact-router 等来协助提供完整的解决方法。

二、React开发环境的搭建

2.1、在浏览器中编写React

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <!--https://cdnjs.com/-->
  <script src="./js/react.development.js"></script>
  <script src="./js/react-dom.development.js"></script>
  <script src="./js/browser.min.js"></script>
  <script src="./js/prop-types.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <!--凡是使用 JSX 的地方,都要加上 type="text/babel"-->
  <script type="text/babel">
    class Hello extends React.Component {
      render() {
        return (<h1>Hello,{this.props.name}</h1>)
      }
    }
    ReactDOM.render(
      <Hello name="zhangsan" />,
      document.getElementById('root')
    );
  </script>
</body>
</html>
复制代码

上面代码一共用了四个库:react.jsreact-dom.jsbrowser.jsprop-types.min.js,它们必须首先加载。

  1. react.jsReact的核心库。
  2. react-dom.js:负责Web页面的DOM操作。
  3. browser.js:将JSX语法转为JavaScript语法。
  4. prop-types.min.js:是传入的props类型的验证。自React v15.5起,React.PropTypes已移入另一个包中,使用prop-types库代替。

需要注意的是从Babel 6.0开始,不再直接提供浏览器直接编译的JS版本,而是要用构建工具构建出来。这里只做演示用。建议学习开发的时候也不要使用浏览器直接引入的方式。

2.2、 搭建本地开发环境

  1. 安装node.js
  2. 安装npm
  3. 全局安装官方推荐脚手架工具create-react-app,类似于Vuevue-cli
npm install -g create-react-app
// 在任意文件夹中使用create-react-app新建项目
create-react-app demo
复制代码

安装成功之后,npm start 或者 yarn start就可以启动项目了。

create-react-app生成的目录结构如下:

demo/
  README.md
  package.json
  yarn.lock
  .gitignore
  node_modules/
  public/
    favicon.ico
    index.html
    ...
  src/
    App.css
    App.js
    App.test.js
    index.css
    index.js
    logo.svg
    serviceWorker.js
复制代码

package.json安装的React依赖介绍如下:

package.jsondependencies可以看出来脚手架工具默认安装了React需要的依赖。下面就介绍这些核心依赖的作用:

  1. react:是React的核心库。
  2. react-dom:负责Web页面的DOM操作。
  3. react-scripts:生成项目所有的依赖。例如babelcss-loader,webpack等从开发到打包前端工程化所需要的react-scripts都帮我们做好了。

现在就可以在App.js里编写我们自己的代码了,如下(已修改生成的源码):

在项目根目录执行npm start(若安装了yarn可使用yarn start),打开http://localhost:3000就可以看到首页了:

建议大家参考Create React App 中文文档

三、Virtual DOM 介绍

要想理解JSX的由来,就要先介绍一下Virtual DOM

一个真实页面对应一个DOM树。在传统页面的开发模式中,每次需要更新页面时,都要手动操作DOM 来进行更新。DOM操作非常昂贵。而且这些操作DOM的代码变得难以维护。

React把真实DOM树转换成JavaScript对象树,也就是Virtual DOM。如下图:

每次数据更新后,重新计算Virtual DOM,并和上一次生成的Virtual DOM做对比,对发生 变化的部分做批量更新。VirtualDOM不仅提升了React的性能,而且它最大的好处其实还在于方便和其他平台集成(比如react-native是基于Virtual DOM渲染出的原生控件)。

因此在Virtual DOM输出的时候,是输出Web DOM,还是Android控件,还是iOS控件,由平台本身决定。

3.1、DOM 元素

Web页面是由一个个HTML元素嵌套组合而成的。当使用JavaScript来描述这些元素的时候,这些元素可以简单地被表示成纯粹的JSON对象。比如,我们现在需要描述一个按钮(button),用HTML 语法表示非常简单:

<button class="primary">
  <em>submit!</em>
</button>
复制代码

其中包括了元素的类型和属性。如果转成JSON对象,会包括元素的类型以及属性:

{
  type: 'button',
    props: {
    className: 'primary',
      children: [{
        type: 'em',
        props: {
          children: 'submit'
        }
      }]
  }
}
复制代码

上面的JS对象表达了一个按钮功能。在表达还不怎么复杂的结构时,书写就已经很难受了。这让我们想起使用HTML编写结构时的简洁。所以JSX语法为此应运而生。假如我们使用JSX 语法来重新表达上述button元素,只需下面这么写:

ReactDOM.render(
  <button className="primary">
    <em>submit!</em>
  </button>
  , document.getElementById('root'))
复制代码

四、JSX介绍

JSXHTML语法直接加入到JavaScript代码中,会让代码更加直观并易于维护。通过编译器转换到纯JavaScript后由浏览器执行。JSX在产品打包阶段都已经编译成了纯JavaScript

注意:JSXJavaScript语言的一种语法扩展,长得像HTML,但并不是HTML。尽管JSX是第三方标准,但这套标准适用于任何一套框架。现在已全部采用BabelJSX编译器来实现对JSX语法的编译。

class Test extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <div>
        <h1 className='title'>Hello,React</h1>
      </div>
    )
  }
}
ReactDOM.render(<Test />, document.getElementById('root'))
复制代码

上面的代码经过编译以后会变成如下:

class Test extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      React.createElement(
        'div',
        null,
        React.createElement(
          'h1',
          { className: 'title' },
          'Hello,React'
        )
      )
    )
  }
}
ReactDOM.render(<Test />, document.getElementById('root'))
复制代码

React.createElement会构建一个JavaScript对象来描述HTML结构的信息,包括标签名、属性、还有子元素等。这样的代码就是合法的JavaScript代码了。

所以从JSX到页面经过了如下图的过程:

4.1、使用 HTML 标签

 ReactDOM.render(<div className="foo">Hello</div>, document.getElementById('root'))
复制代码

HTML里的classJSX里要写成className,因为classJS 里是保留关键字。同理某些属性比如for要写成htmlFor

4.2、使用 JavaScript 表达式

JSX遇到HTML标签(以<开头),就用HTML规则解析。遇到代码块(以 { 开头),就用 JavaScript规则解析。

const names = ['zhangsan', 'lisi', 'wangwu']
ReactDOM.render(
  <div>
    {
      names.map((name,key) => {
        return <div key={key}>Hello,{name}</div>
      })
    }
  </div>,
  document.getElementById('root')
)
复制代码

JSX允许直接在模板插入JavaScript变量。如果这个变量是一个数组,则会展开这个数组的所有成员。

const names = [<div key="1">zhangsan</div>, <div key="2">lisi</div>]

ReactDOM.render(
  <div>
    {names}
  </div>,
  document.getElementById('root')
)
复制代码

4.3、 注释

JSX里使用注释也很简单,就是沿用JavaScript,唯一要注意的是在一个组件的子元素位置使用注释要用 {} 包起来。

const App = (
  <Nav>
    {/* 节点注释 */}
    <Person
      /* 多行
     注释 */
      name={window.isLoggedIn ? window.name : ''}
    />
  </Nav>
)
复制代码

4.4、 HTML转义

React会将所有要显示到DOM的字符串转义,防止XSS。所以如果JSX中含有转义后的实体字符比如 &copy (©) 最后显示到 DOM中不会正确显示,因为React自动把&copy中的特殊字符转义了。可以使用dangerouslySetInnerHTML来实现。

ReactDOM.render(
  <div dangerouslySetInnerHTML={{ __html: '&copy 2020' }} />,
  document.getElementById('root')
)
复制代码

4.5、自定义 HTML 属性

如果在JSX中使用的属性不存在于HTML的规范中,这个属性会被忽略。如果要使用自定义属性,可以用data-前缀。可访问性属性的前缀aria-也是支持的。

 ReactDOM.render(<div data-attr="abc">content</div>, document.getElementById('root'))
复制代码

五、组件化

React认为组件是和模板紧密关联的,组件模板和组件逻辑分离让问题复杂化了。React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。一个React应用就是构建在React 组件之上的。Component(组件)可以是类组件(class component)、函数式组件(function component)。

组件有三个核心概念,React组件基本上由组件的构建方式组件内的属性状态生命周期方法组成。如下图:

  • props(属性)
  • states(状态)
  • 生命周期方法

注意:组件生成的 HTML 结构只能有一个单一的根节点。在React中,数据是自顶向下单向流动的,即从父组件到子组件。

5.1、组件构建方法

官方在React组件构建上提供了3种不同的方法:React.createClassES6 classes 和无状态 函数(stateless function)。

官方在React@15.5.0后不推荐用React.createClass,建议使用ES6 class,这里不做具体介绍。

ES6 classes

class Test extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (<h1>Hello,React</h1>)
  }
}
复制代码

无状态组件

可以用纯函数来定义无状态的组件(stateless function),这种组件没有状态,没有生命周期,只是简单的接受props渲染生成DOM结构。无状态组件非常简单,开销很低。比如使用箭头函数定义:

const Hello = (props) => <div>Hello {props.name}</div>

ReactDOM.render(<Hello name="张三" />, document.getElementById('root'))
复制代码

5.2、props

props就是组件的属性,由外部通过JSX属性传入设置,一旦初始设置完成,就可以认为this.props 是不可更改的,所以不要轻易更改设置this.props里面的值。

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

// PropTypes 验证,若传入的props type不是string将提示错误
Hello.propTypes = {
  name: PropTypes.string
}

// Prop 初始值,如果 prop 没有传入值將會使用 default 值 lisi
Hello.defaultProps = {
  name: 'lisi'
}

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

复制代码

5.3、state

state是组件的当前状态,用this.state来存取state。一旦状态(数据)更改,组件就会自动调用 render重新渲染UI,这个更改的动作会通过this.setState方法来触发。

下面代码是一个1000毫秒就會加一的累加器:

class Timer extends React.Component {
  constructor(props) {
    super(props)
    // 需要自行绑定 this context
    this.tick = this.tick.bind(this)
    this.state = {
      count: 0
    }
  }
  // 累加器方法,每一秒会使用 setState() 更新内部 state,让 Component 重新 render
  tick() {
    this.setState({ count: this.state.count + 1 })
  }
  // 生命周期函数
  componentDidMount() {
    this.interval = setInterval(this.tick, 1000);
  }
  // 生命周期函数
  componentWillUnmount() {
    clearInterval(this.interval);
  }
  render() {
    return (
      <div>Count: {this.state.count}</div>
    )
  }
}
ReactDOM.render(<Timer />, document.getElementById('root'))
复制代码

5.4、生命周期方法

每个组件都包含都包含组件生命周期方法,在运行过程中特定的阶段执行这些方法。

组件的生命周期分为四类,共有10个方法:

  1. 挂载
  2. 更新
  3. 卸载
  4. 错误处理

下面会依次介绍这几类组件的生命周期所调用的函数。

挂载

当组件实例被创建并插入DOM中时,其生命周期调用顺序如下:

  • constructor()
  • static getDerivedStateFromProps():会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。
  • render():是class组件中唯一必须实现的方法。
  • componentDidMount():在组件挂载后(插入DOM树中)立即调用。

更新

当组件的propsstate发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate():当propsstate发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
  • render()
  • getSnapshotBeforeUpdate():在最近一次渲染输出(提交到DOM节点)之前调用。
  • componentDidUpdate():会在更新后会被立即调用。首次渲染不会执行此方法。

卸载

当组件从DOM中移除时会调用如下方法:

  • componentWillUnmount():在组件卸载及销毁之前直接调用。

错误处理

当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

  • static getDerivedStateFromError():此生命周期会在后代组件抛出错误后被调用。
  • componentDidCatch():此生命周期在后代组件抛出错误后被调用。

这里只做简单介绍,后面会单独写文章详细记录生命周期的使用方式。

六、事件处理

React里面绑定事件的方式和在HTML中绑定事件类似,使用驼峰式命名指定要绑定的onClick属性为组件定义的一个方法。下面是一个数字累加的小例子:

class AddCount extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
  }
  handleClick(e) {
    this.setState({ count: this.state.count + 1 });
  }
  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick.bind(this)}>Click me</button>
      </div>
    )
  }
}
ReactDOM.render(<AddCount />, document.getElementById('root'))
复制代码

注意要显式调用bind(this)将事件函数上下文绑定要组件实例上,这也是React推崇的原则:没有黑科技,尽量使用显式的容易理解的JavaScript代码。

七、 DOM操作

有时候我们避免不了要直接操作DOMReact也提供了几种我们可以直接操作DOM的方式。

findDOMNode

当组件加载到页面上之后(mounted),可以通过react-dom提供的findDOMNode()方法拿到组件对应的DOM元素。

class Test extends React.Component {
  constructor(props) {
    super(props)
  }
  componentDidMount() {
    const el = ReactDOM.findDOMNode(this)
    // 123456
    console.log(el.textContent)
  }
  render() {
    return (
      <div>123456</div>
    )
  }
}
ReactDOM.render(<Test />, document.getElementById('root'))
复制代码

注意:findDOMNode() 不能用在无状态组件上。findDOMNode 仅在组件始终返回永不更改的单个 DOM 节点时才起作用。在<React.StrictMode>严格模式下,这个方法已经被官方弃用,所以在开发中不要使用这个方法。

Refs

另外一种方式就是通过在要引用的DOM元素上面设置一个ref属性指定一个名称,然后通过 this.refs.name来访问对应的DOM元素。

 class Test extends React.Component {
  constructor(props) {
    super(props)
  }
  componentDidMount() {
    this.refs.textInput.focus()
  }
  render() {
    return (
      <div>
        <input ref="textInput" />
      </div>
    )
  }
}
ReactDOM.render(<Test />, document.getElementById('root'))

复制代码
  1. 不要在render或者render之前访问refs
  2. 不要滥用refs。比如只是用它来按照传统的方式操作界面 UI:找到 DOM -> 更新 DOM

参考

https://zh-hans.reactjs.org/

http://huziketang.mangojuice.top/books/react/

https://www.bilibili.com/video/BV1g4411i7po?p=2

https://www.jianshu.com/p/c6040430b18d

https://www.zhihu.com/question/336664883/answer/790855896

https://book.douban.com/subject/26918038/

https://www.kancloud.cn/digest/babel/217110

http://www.ruanyifeng.com/blog/2015/03/react.html

https://kdchang.gitbooks.io/react101/content/

https://github.com/mocheng/react-and-redux/issues/99

https://juejin.im/entry/587de1b32f301e0057a28897

https://www.html.cn/create-react-app/docs/getting-started/

本文使用 mdnice 排版

这篇关于React从0到1系列第二章——React的使用方式和基础API介绍的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!