react

261 阅读7分钟

官方文档:react.docschina.org/

一、react简介

概述

用于构建用户界面的JavaScript库。主要用于构建UI,很多人认为React是MVC结构中的V(view)

MVC结构

  • M:Model 模型
  • V:View 视图 UI界面
  • C:controller 控制器

MVVM

  • M:Model 模型
  • V:View 视图 UI界面
  • VM:view modle

目前主流的前端框架

  • Vue.js 最火(关注度最高)
  • React.js 最流行
  • Angular.js

使用方法

  • 浏览器直接使用
  • 通过npm + webpack构建项目进行使用
  • 脚手架,比如Create React App(这种方式用的比较多)

二、使用

基本使用

创建一个React.js组件 react.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  
    <!-- 加载我们的 React 组件。-->
    <!--<script src="script.js"></script> 如果写在这里,代码按照顺序执行,js文件里面取元素会取不到-->
</head>
<body>
    <div id="container"></div>
    <script src="./script.js"></script>
</body>
</html>

script.js 这里是原生的API调用

//定义一个Reactt.js组件
class  Hello extends React.Component{
    render(){
        return React.createElement(
            "div",
            null,
            "hello react"
        );
    }
}
ReactDOM.render(React.createElement(Hello),document.getElementById("container"))

jsx

jsx是一个 JavaScript 的语法扩展,在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。

script.js中改为以下写法,但是浏览器还不支持这种新语法,因此要使用babel

//jsx 语法糖,在解析的时候还是会转为原生API的调用
class  Hello extends React.Component{
    render(){
        return (
            <div>
                Hello React
            </div>
        );
    }
}
ReactDOM.render(<Hello/>,document.getElementById("container"))

jsx+webpack+babel

由webpack使用babel-loader来去驱动babel编译jsx的指令

index.html不需要引入script.js,因为webpack会进行打包

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
  
</head>
<body>
    <div id="container"></div>
   
</body>
</html>

安装Babel

preser-env是es6语法的插件

npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-react

配置babel

.babelrc.json配置文件中添加预设和单独使用的插件

{
    "presets": ["@babel/preset-env","@babel/preset-react"],
    "plugins": []
}

安装babel-loader

npm install babel-loader --save-dev

配置babel-loader

在webpack的配置文件 webpack.config.js 中配置babel-loader,在 module的rules中配置,options不写是因为babel自己的配置文件中已经写过用什么预设了

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module:{
    rules:[
      {
        test:/\.js$/,
        exclude:/(node_modules|bower_components)/,
        use:{
          loader:'babel-loader',
          //options:{
            //presets:['@babel/preset-env']
          //}
        }
      }
    ]
  }
};

使用npm安装react

之前都是在script中引入网络上的react,现在使用npm安装react
过程如下

现在工程中只有一个index.html一个页面,但有很多组件,通过放在容器中进行显示

将组件独立

jsx的基本语法

jsx中使用数值、字符串、boolean、数组等数据
return中必须要有一个根节点(与vue相同)
当使用 JSX 语法时,可以使用大括号在 JSX 中插入 JavaScript 表达式

三、组件之间数据传输

React组件中和数据有关的有三个东西(state,props,context)

1、组件自身数据

数据被挂载到组件的实例上
render函数每次被调用一次,页面就被再次绘制一次

  • 1、每个class组件都可以设置自身的数据
    //构造函数,实例对象一创建,构造函数就会被调用
    constructor(){
        super()
        this.a = 100
    }
  • 2、组件自身的数据发生变化,不会引起视图改变(不会触发render函数) 自己手动调用render函数并不能改变数据,因为render函数是react组件生命周期函数之一

2、闭包内数据

闭包中的数据,也不会引起视图改变

import React, { Component } from 'react'

export default class ClosureData extends Component {


    render() {
        //console.log("render",this) selfdata
        console.log("render is called")

        var a = 100
        return (
            <div>
                <h1>闭包内的数据</h1>
                <p>{a}</p>
                <p>
                    <input type="button" value="click me" onClick={()=>{ //闭包
                        a++
                        console.log(a)
                    }}/>
                </p>
            </div>
        )
    }
}

3、state中的数据

state中的数据发生改变后,会触发组件重新渲染(render函数会被调用)
模型中的数据一发生变化,视图中的数据就会发生变化,这是mvvm中的vm在进行操作,帮助我们不用去做各种dom操作,比如

var a = document.getElementById()

StateDate.js

import React, { Component } from 'react'

export default class StateData extends Component {
    constructor(){
        super()
        //将数据放入到state中
        this.state = {a:100}
    }

    add(){
        console.log("add is called")
        //this.state.a++ 这种写法错误
        this.setState({a:this.state.a+1})
    }
    render() {
        console.log("render is called")

        return (
            <div>
                <h1>state中的数据</h1>
                <p>{this.state.a}</p>
                <p>
                    <input type="button" value="click me" onClick={this.add.bind(this)}/>
                </p>
            </div>
        )
    }
}

4、props

react中父组件可以通过属性将数据传递给子组件,在子组件中通过props 对象获取{this.props.xxx}
可以通过propstype来限定子组件获取的属性必须为哪种类型和是否传递,

  • 首先安装prop-types npm install prop-types -save
  • 在子组件中导入 import PropTypes from "prop-types"
  • 规定属性的类型以及是否必要
PropsChild.propTypes = {
    name: PropTypes.string.isRequired //必须要传递一个string过来
}

子组件将获取到的数据的值传给自己的数据,不能在构造函数中直接使用this.props.xxx,因为这里props获取不到,在render里面才有props

    constructor(){
        super()
        this.props.a  //错误写法
    }
    render() {
        console.log(this.props)
        return (
            <div>
                <h3>这是子组件</h3>
                <p>{this.props.a}</p>
            </div>
        )
    }

可以在构造函数中添加一个props参数,以此获取props

    constructor(props){
        super()
        this.state={
            a: props.a
        }
    }

子组件不能直接修改父组件的数据,可以通过父组件向子组件传递一个函数,子组件中通过调用这个函数,将数据传递给父组件

PropsParent.js

import React, { Component } from 'react'
import PropsChild from "./PropsChild"
export default class PropsParent extends Component {

    constructor(){
        super()
        this.state={
            a:100
        }
    }

    setA(a){
        this.setState({a:a})
    }
    render() {
        return (
            <div>
                <h1>这是父组件</h1>
                <p>{this.state.a}</p>
                ------------------
                <PropsChild a={this.state.a } setA={this.setA.bind(this)}/> {/*这里的this指向父类的实例*/}
            </div>
        )
    }
}

PropsChild.js

import React, { Component } from 'react'
import PropTypes from "prop-types"

export default class PropsChild extends Component {

    constructor(props){
        super()
        this.state={
            a: props.a  //获取父组件传递的值
        }
    }

    add(){
        this.setState({a:this.state.a + 1})
        this.props.setA(this.state.a + 1) //根据父组件提供的函数,将自己修改后的值传回去
    }
    render() {
        return (
            <div>
                <h3>这是子组件</h3>
                <p>{this.props.a}</p>
                <p>
                    <input type="button" value="click me" onClick={this.add.bind(this)}/>
                </p>
            </div>
        )
    }
}

四、react创建组件

1、使用class关键字创建组件

rcc
编写一个类继承于Component,并向外暴露

传参的一种方式

另一种方式(扩展运算符)

class中,如果没有定义构造函数,则默认提供一个无参的构造函数;如果自定义了一个构造函数,则必须调用super,只有调用了super以后才能使用this。
调用this.state将数据放入state管理,在state中的数据如果发生变化,会触发组件重新渲染

state数据的修改
不能直接修改State,而是通过setState来进行修改
state的更新可能是异步操作 调用setState,组件中的state数据并不会立即改变,setState只是把修改的状态放入到一个队列中,React会优化执行的时机,并且出于性能的原因,可能会将多次setState的状态修改合并成一次,所以不要依赖当前的state去计算下一个state

doAdd(){
    this.setState({count:this.state.count + 1})
    console.log("第一次修改",this.state.count)
    this.setState({count:this.state.count + 1})
    console.log("第二次修改",this.state.count) 
    this.setState({count:this.state.count + 1})
    console.log("第三次修改",this.state.count)
    //1 1 1
}

要解决这个问题,可以让setState()接收一个函数而不是一个对象,这个函数用state作为参数

doAdd(){ //点击一次按钮count加 3
    this.setState(state =>({
      count: state.count + 1  
    }),()=>{
        console.log(this.state.count)
    }) //4  回调函数,输出结果是4,说明是异步操作
    this.setState(state =>({
      count: state.count + 1  
    }))
    this.setState(state =>({
      count: state.count + 1  
    }))

}

2、使用函数创建组件

快捷方式(rsc)
编写一个function,返回jsx

import React, { Component } from 'react'

function ConstructorEmp(){
    return (
        <div>
            <h1>构造函数创建组件</h1>
        </div>
    )
}

export default ConstructorEmp

函数组件中可以获取props,通过props获取父组件传来的数据。但是函数组件中没有this,也不能使用this.state

function ConstructorEmp(props){
    return (
        <div>
            <h1>构造函数创建组件</h1>
                <p>{props.name}</p>
                <p>{props.age}</p>
                <p>{props.gender}</p>
        </div>
    )
}

五、组件的渲染方式

条件渲染

通过if进行条件渲染

function Conditional(props){
    const showGreeting = () => {
        if(props.isLogin){
            return(<div>用户已登录</div>)
        }else{
            return (<div>用户没有登录</div>)
        }
    }
    return (
    <div>{showGreeting()}</div>
    )
}

使用&&进行条件渲染

function Conditional(props){
    return props.isLogin && (<div>用户已登录</div>)
}

使用三元运算符

function Conditional(props){
    return props.isLogin?(<div>用户已登录</div>):(<div>用户没有登录</div>)
}

组织组件的渲染

function Conditional(props){
    if(!props.isLogin){
        return null
   }
    return props.isLogin?(<div>用户已登录</div>):(<div>用户没有登录</div>)
}

列表渲染

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key
当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key

import React from 'react'

function List(props) {
    return (
        <ul>
            {
                props.emps.map((item,index)=>{
                    return (<li key={index}>{item.name}</li>)
                })
            }
        </ul>
    )
}

export default Listconst emps = [
    {name :"tom",age:18,gender:"男"},
    {name :"jack",age:20,gender:"男"}
]
ReactDOM.render(<List emps={emps}/>,document.getElementById("container"))

六、fragments

react.docschina.org/docs/fragme… 什么时候用——(想要去除多余的div标签时) 比如父组件要做一个表格,子组件是里面的各个格子,里面如果有div的话会出错,因此可以使用 下面这个标签

<React.fragments></React.fragments>

七、组合与继承

react.docschina.org/docs/compos…
props.children
不推荐继承

包含关系

有些组件无法提前知晓它们子组件的具体内容,这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中

子组件

function FancyBorder(props) {
  return (
    <div >
      {props.children}
    </div>
  );
}

父组件,FancyBorder标签之间的内容就是要传给子组件的内容

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 >
        Welcome
      </h1>
      <p>
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。

function SplitPane(props) {
  return (
    <div>
      <div>
        {props.left}
      </div>
      <div>
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

评论列表练习

css命名冲突

当前的样式文件一旦在组件中引入后,就变成全局的样式,所有有时候会产生命名的冲突,可以使用css模块化解决这个问题
配置webpack中的css-loader,在webpack.config.js中开启css-loader模块化设置 www.webpackjs.com/loaders/css…

      {
        test: /\.css$/,
        use: [{
            loader: "style-loader" 
        }, {
            loader: "css-loader",
            options:{
              modules:{
                localIdentName:'[path][name]__[local]--[hash:base64:5]'
              }
            } 
        }]
      }

但凡以元素名称作为选择器,都不会被模块化,id和class都会被模块化

八、reactstrap

reactstrap是基于bootstrap的,能更方便的使用bootstrap中的组件 reactstrap.github.io/?from=madew…

安装

在安装reactstrap之前要安装bootstrap

npm install --save bootstrap reactstrap

导入bootstrap.css

import "bootstrap/dist/css/bootstrap.min.css"

导入组件

import { Button } from 'reactstrap';

使用

join() 方法用于把数组中的所有元素放入一个字符串。

元素是通过指定的分隔符进行分隔的。

import React from 'react'

//因为开启了模块化,所以要用模块化的写法
import style from "bootstrap/dist/css/bootstrap.min.css"
import { Button } from 'reactstrap';
function ReactStrap() {
    return (
        <div>
            <Button className={[style.btn,style["btn-primary"]].join(" ")}>primary</Button>{' '}
        </div>
    )
}

export default ReactStrap

九、事件处理

react.docschina.org/docs/handli…

class LoggingButton extends React.Component {
  // 此语法确保 `handleClick` 内的 `this` 已被绑定。
  // 注意: 这是 *实验性* 语法。
  handleClick = () => {
    console.log('this is:', this);
  }// [Object Object]

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

这种写法会遇到不支持箭头函数的情况,需要使用@babel/plugin-proposal-class-properties插件

npm install --save-dev @babel/plugin-proposal-class-properties

在babelrc文件中配置插件

"plugins":[
    @babel/plugin-proposal-class-properties
]

十、refs

react.docschina.org/docs/refs-a…
ref 是一个入口 允许我们直接访问DOM元素或组件实例。

创建refs

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
//创建一个ref的引用
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

关联ref

<input type="text" ref={this.myRef}>

访问refs

当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 的 current 属性中被访问。 const node = this.myRef.current; ref 的值根据节点的类型而有所不同:

  • 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。

  • 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。 你不能在函数组件上使用 ref 属性,因为他们没有实例。

//点击事件
clickHandle()=>{
    console.log(this.myRef.current.value)
}

为DOM元素添加ref

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 创建一个 ref 来存储 textInput 的 DOM 元素
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 输入框获得焦点
    // 注意:我们通过 "current" 来访问 DOM 节点
    this.textInput.current.focus();
  }

  render() {
    // 告诉 React 我们想把 <input> ref 关联到
    // 构造器里创建的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

为class组件添加ref

在父组件中点击按钮,使得子组件中的文本框获取焦点

将DOM refs暴露给父组件

使用ref转发

//refs转发。React.forwardRef()接收props和ref,
//可以将父组件的ref传递给子组件内的节点
const TextInput3 = React.forwardRef((props,ref) => {
    return(
        <div>
            <input type="text" ref={ref}></input>
        </div>
    )
})
<TextInput3 ref={this.textInput3}/>

回调refs

你可以在组件间传递回调形式的 refs,就像你可以传递通过 React.createRef() 创建的对象 refs 一样。

import React, { Component } from 'react'


function CustomTextInput(props) {
    return (
      <div>
        <input ref={props.inputRef} />
      </div>
    );
  }
export default class PassCallBackRef extends Component {

    handleClick(){
        console.log("子组件中的数据:",this.inputElement.value)
    }
    render() {
        return (
            <div>
                <CustomTextInput  
                    inputRef={el => this.inputElement = el}
                />
                <button onClick={this.handleClick.bind(this)}>点我获取数据</button>
            </div>
        )
    }
}

在上面的例子中,Parent 把它的 refs 回调函数当作 inputRef props 传递给了 CustomTextInput,而且 CustomTextInput 把相同的函数作为特殊的 ref 属性传递给了 <input>。结果是,在 Parent 中的 this.inputElement 会被设置为与 CustomTextInput 中的 input 元素相对应的 DOM 节点,父组件可以获取子组件节点的数据。

回调ref 传递

功能:点击按钮获取子组件中的数据

import React, { Component } from 'react'
import { func } from 'prop-types'

function TextInput (props){
    return(
        <div>
            <input type="text" ref={props.inputRef}/>
        </div>
    )
}
export default class PassCallBackRef extends Component {

    HandleClick = ()=>{
        console.log("子组件中的数据:",this.input.value)
    }
    render() {
        return (
            <div>
                {/*将子组件作为参数传递给input */}
                <TextInput inputRef={el=>{ this.input = el}}></TextInput>
                <button onClick={this.HandleClick}>点我获取子组件的数据</button>
            </div>
        )
    }
}

十一、受控组件与非受控组件

受控组件

假设我们现在有一个表单,表单中有一个input标签,input的value值必须是我们设置在constructor构造函数的state中的值,然后,通过onChange触发事件来改变state中保存的value值,这样形成一个循环的回路影响。也可以说是React负责渲染表单的组件仍然控制用户后续输入时所发生的变化。

十二、高阶组件

属性代理

使用refs获取组件实例

受控组件state抽取

用其他元素包装组件

使用反向继承实现高阶组件

反向继承:返回一个组件继承传入的被包裹组件
一旦一个类继承另一个类的时候,就自动拥有父类中所有非私有的成员信息

使用反向继承渲染劫持

高阶组件.js

const elementsTree = super.render()
let newProp = {style:{color:"red"}};
const props = {...elementsTree.props, ...newProp}
const newElement = React.cloneElement(elementsTree,props,)

十三、生命周期函数

react.docschina.org/docs/react-…
组件渲染与挂载blog.csdn.net/u012131835/… 我们把组件渲染,并且构造DOM元素插入到页面的过程称为组件的挂载。

构造函数

constructor函数只会执行一次

render()

页面中有一个真实的dom树,react中为了性能考虑,拷贝了一个虚拟的dom树,当DOM节点发生变化时,react会将两个DOM树进行比较,将有差异的地方进行更新

  • render()中会构建虚拟DOM,进行diff算法,更新dom树
  • 此时虚拟dom还没有渲染到页面上
  • render会执行多次

componentDidMount

  • 构建的虚拟DOM已经挂载到了页面上
  • 只会执行一次

componentDidUpdate

  • 组件完成更新,此时页面已经是最新的了
  • 会执行多次

componentWillUnmount

  • 组件实例将要被销毁,此时组件还可以被使用
  • 通常在此函数中处理一些资源的释放

十四、hook

1、useState

  • useState这个hook允许我们在函数组件中声明state
  • useState中的set方法,只会在数据的地址发生改变的时候重新渲染

2、useEffect

相当于componentDidMount 和 componentDidUpdate
副作用分为需要清除的副作用和不需要清除的副作用

十五、addwebpackAlias 文件夹路径起别名

addwebpackAlias

const path = require("path")
const {addwebpackAlias} = require("cusomize-cra")
addwebpackAlias({
    ["@"]:path.resolve(_dirname,"src")
})