React 学习笔记

本文详细介绍了React的基础知识,包括脚手架安装、Hello React、JSX语法、数据绑定与响应式设计、事件处理、循环渲染、组件拆分、父子组件通信以及React的生命周期等核心概念。此外,还探讨了Redux的基础用法和数据管理,以及如何调试React应用。通过本文,读者可以系统地学习React开发。

React

1. 脚手架的安装

下载:

npm install -g create-react-app

可能会报错,有可能是因为没有权限,以管理员身份运行,就可以了 。

C:\Windows\system32>npm install -g create-react-app
D:\TEND\node\create-react-app -> D:\TEND\node\node_modules\create-react-app\index.js
+ create-react-app@4.0.1
added 67 packages from 25 contributors in 4.77s

创建项目:

// 创建文件夹
mkdir ReactDemo
// 创建项目
create-react-app demo01
// 进入项目
cd demo01
// 运行项目
npm start

项目目录:

node_modules  包管理文件
public
--- favicon.ico  项目图标
--- index.html  项目页面
--- manifest.json  移动端适配文件
src
--- index.js   项目入口文件
--- index.css  项目的全局样式文件

--- App.js   组件(模块化开发)
--- App.css  组件的样式文件

--- reportWebVitals.js  用于移动端的(离线缓存功能)
--- setupTests.js  DOM 断言的适配器
--- App.test.js  

.gitignore  git 省略文件
package-lock.json  包版本和下载地址管理文件
package.json  项目管理文件
README.md  项目说明文件

你可以在 src 中创建子目录。 为了加快重新构建的速度,Webpack 只处理 src 中的文件。 你需要将任何 JS 和 CSS 文件放在 src,否则 Webpack 将发现不了它们。

2. Hello react

1,删除 src 中的所有文件,从头开始写:

2,创建项目的入口文件: index.js

import React from 'react'
import ReactDOM from 'react-dom'
// 导入 App 组件,
import App from './App'

// 把 组件 放到 页面模板上,id 为 root 的元素上
ReactDOM.render(<APP />, document.getElementById('root'))

3,创建 App.js 组件,组件必须以大写开头;

import React, { Component } from 'react'

// 等于以下代码
// import React from 'react'
// const Component = React.Component

class App extends Component {
    render () {
        return (
        	<div>Hello zyf</div>
        )
    }
}

export default App

3. jsx

1. 概念:

​ js 和 xml 结合,遇到 < 就编译为 html 语法, 遇到 {} 就编译为 js 语法;

这个语法,让 react 可以更方便创建 虚拟DOM,创建html结构;

没有 jsx 之前,react 创建虚拟DOM 的方法:

class App extends Component {
    render () {
        var child1 = React.createElement('li', null, '你好')
        var child2 = React.createElement('li', null, '我是 zyf啊')
        var root = React.createElement('ul', {className: 'my-list'}, child1, child2)
    }
}

有 jsx 后就简单的:

class App extends Component {
  render () {
    // jsx: js 和 xml 结合,遇到 < 就编译为 html 语法, 遇到 {} 就编译为 js 语法;
    return (
      <div>
        <div>Hello zyf</div>
        <ul>
          <li>{false ? '你好' : 'false哦'}</li>
          <li>我是 zyf啊</li>
        </ul>
      </div>
    )
  }
}


2. () 的作用:

有了 () 我们就可以使 html 结构换行显示;


3. Frangment 标签:

在 React 要求必须在组件的最外层进行包裹,可以使用 div 进行包裹 或使用 teact 的 标签

使用 Fragment 标签后,他不会进行渲染;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ol3d6IVr-1608447252144)(H:\笔记本\images\React\使用 Fragment 标签.png)]

以下是代码, Frangment 标签需要从 react 中导入:

// App 组件
import React, { Component, Fragment } from 'react'
import './App.css'

class App extends Component {
  render () {
    // jsx: js 和 xml 结合,遇到 < 就编译为 html 语法, 遇到 {} 就编译为 js 语法;
    return (
      <Fragment>
        <div>Hello zyf</div>
        <ul>
          <li>{false ? '你好' : 'false哦'}</li>
          <li className="my-mane">我是 zyf啊</li>
        </ul>
      </Fragment>
    )
  }
}

export default App


4. 样式使用:

**在 jax 中的 class 样式名称不是 lcass="xxx",而是 className="xxx";**因为这个 class 名字可以会和 创建 组件 时的 Class 混乱了 。

// App 组件
import React, { Component, Fragment } from 'react'
import './App.css'

class App extends Component {
  render () {
    return (
      <Fragment>
        <div>Hello zyf</div>
        <ul>
          <li>{false ? '你好' : 'false哦'}</li>
          <li className="my-mane">我是 zyf啊</li>
        </ul>
      </Fragment>
    )
  }
}

export default App


5. jsx 代码注释:

JSX中的代码注释是非常有讲究的 ,具体的方法是这样的 {/* xx */}, 而不是 // 或者 /**/

快捷键是:Ctrl + / (可能不同的编辑器的快捷键不同,可以百度一下);

看代码咯:

<ul>
  {
    this.state.list.map((item, index) => {
      return (
        <li key={item+index}>
          // 内容   => 错误的
          {item} 
          {/* 删除按钮 */}  => 正确的
          <button
            onClick={this.deleteList.bind(this, index)}>
              删除
          </button>
        </li>
      ) 
    })
  }
</ul>

注意

  • 可能要在 <Frangment> 标签中才能生效;

6. 解析 html 标签:

如果想要把 html 标签解析成 和在html页面上的标签一致的效果,就可以使用 dangerouslySetInnerHTML

<ul>
  {
    this.state.list.map((item, index) => {
      return (
        <li key={item+index} className="d-flex">
          {/* 内容 */}
          <span dangerouslySetInnerHTML={{__html: item}}></span>
          {/* 删除按钮 */}
          <button
            onClick={this.deleteList.bind(this, index)}>
              删除
          </button>
        </li>
      ) 
    })
  }
</ul>

7. label 的区别:

label 的作用是:用户点击 labe 中的文字时, 焦点可以自动的定位到 input 元素上面 。

我们按照平时的 html 语法是这样写的:

 render () {
    return (
      <Fragment>
        <label for="inputId">添加学习忍术:</label>
        <input 
            id="inputId" 
            value={this.state.inputValue} 
            onChange={this.inputChange.bind(this)}
         />
        <button onClick={this.addList.bind(this)}>学习忍术</button>
	}
}

但这时,控制台会报错,说要把 for 这个属性改成 htmlFor 这种格式:

 <label htmlFor="inputId">添加学习忍术:</label>
<input 
    id="inputId" 
    value={this.state.inputValue} 
    onChange={this.inputChange.bind(this)}
 />

4. 数据绑定 和 响应式设计

1. 数据绑定

在 组件 class 中使用 constructor 来存放数据,vue 为 data:

主要代码:

  constructor (props) {
    super(props)    // 调用父类的构造函数,固定写法
    this.state = {   // 存放数据
      xxxx: 'xxxx',
      list: []
    }
  }

创建好数据后,为 输入框 绑定数据,他不和 vue 一样是 双向数据绑定的, react 是 单向数据流

input 中使用

<input value={ this.state.inputValue }/>

this.state 中的数据进行绑定;

但是会报错,说这样在 input 中,这个数据只能只读,不能修改 。 大概是这个元素,有绑定数据,通过事件实现 数据的双向绑定;

index.js:1 Warning: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.

2. 响应式设计

所以,我们要为 input 进行绑定事件,然后把 input 的值,赋值到 this.state 中:

<input value={this.state.inputValue} onChange={this.inputChange}/>

事件处理函数 写在 class Naruto extends Component 中:

class Naruto extends Component {
  inputChange (e) {
    // 获取 input 中输入的数据
    const data = e.target.value
  }
}

然后,我们要把获取的值,赋值到数据中:

要注意格式,和微信小程序一样的

  inputChange (e) {
    // 获取 input 中输入的数据
    const data = e.target.value
    // 赋值到 this.start 中
    this.setState({
      inputValue: data
    })
  }

不能是这样:错误错误错误 。

inputChange (e) {
    // 获取 input 中输入的数据
    const data = e.target.value
    // 赋值到 this.start 中
    this.state.inputValue = data;
}

还会报错,因为 函数中的 thisundefined,所以我们要传递 this 过去:

 <input value={this.state.inputValue} onChange={this.inputChange.bind(this)}/>

在函数中,所以 this 就是指向 input 了 。

以下代码:

// 鸣人组件
import React, { Component, Fragment } from 'react'

class Naruto extends Component {
  // 继承父类的 props 参数,Naruto 继承了 Component
  constructor (props) {
    super(props)    // 调用父类的构造函数,固定写法
    // 存放数据
    this.state = {
      inputValue: '你好',
      list: []
    }
  }
  // jsx 语法 ( 模板
  render () {
    return (
      <Fragment>
        <input value={ this.state.inputValue }/>
        <button>学习忍术</button>

        <ul>
          <li>隐分身</li>
          <li>螺旋丸</li>
        </ul>
      </Fragment>
    )
  }
}

export default Naruto


5. react 中的事件

注意:

1. 绑定事件:
  • 要添加 on, 如 onChangeon 后面必须 大写;

  • 进行事件绑定时,不用双引号,事件名用 大括号 括起来

    <input onChange={this.inputChange} />
    
  • class Naruto extends Component 写方法事件 和 数据 , jsx 同级,他们不需要使用 逗号分隔

    class Naruto extends Component {
      // 数据
      constructor (props) {
          .....
      }
        
      // jsx 语法 ( 模板
      render () {
        return (
            .....
        )
      }
        
      // 事件
      inputChange (e) {
        console.log(e)
      }
    }
    

2. this指向:
  • 触发事件中的 this 指向, 他可能为 undefined ,所以在 绑定事件时,可以使用 bind() 修改事件中的 this 指向 。

    <input value={this.state.inputValue} onChange={this.inputChange.bind(this)}/>
    
    inputChange (e) {
        // 获取 input 中输入的数据
        const data = e.target.value
    	console.log(this)
    }
    

在这里插入图片描述


3. 修改数据:

修改 react 中的数据:和微信小程序一样 。

this.setState({
    xxxx: xx
})

还有一个要注意的是 修改数据时,不能直接修改里面的值(不能像 vue 一样);

他要拿到值,然后修改,然后再放到 数据里面 ;

记住React是禁止直接操作state的

class xx extends Component {
    constructor (props) {
        super(props)
        this.state = {
            list: ['xxx', 'xxx']
        }
    }
    xxx () {
        
        // 错误
         this.setState({
    		list: this.state.list.splice(1,1);
		})
        
        // 正确
        const lists = this.state.list
        list.splice(1,1)
        this.setState({
            list: lists
        })
    }
}


4. 获取数据:

获取 react 中的数据,要注意一下,不能和 vue 一样

class Naruto extends Component {
  // 数据
  constructor (props) {
    super(props)    // 调用父类的构造函数,固定写法
    this.state = {
      inputValue: '',
      list: ['隐分身', '螺旋丸']
    }
  }
}

// 正确
this.state.inputValue
// 错误
this.inputValue

5. 获取函数对象中的参数:
 inputChange (e) {
    // e.target.value 元素内容
  }

6. 传递参数:

函数传递参数,都携带在 bind() 的第二个参数中,然后在处理函数中接收 。

<ul>
  {
    this.state.list.map((item, index) => {
      return (
        <li key={item+index}>
          {item} 
          <button
            onClick={this.deleteList.bind(this, index)}>
              删除
          </button>
        </li>
      ) 
    })
  }
</ul>

  // 点击删除
  deleteList (index) {
    // react 不能直接修改 state 中的数据,要拿出来后,再进行修改,在赋值进去
    // 获取值
    const lists = this.state.list;
    // 修改值
    lists.splice(index, 1);
    // 把处理后的值,放到 state 中
    this.setState({
      list: lists
    })
  }

1. onChange

概念

input 输入触发事件;



6. react 的循环渲染

1. map() 循环

在 jsx 中循环渲染数据,不能像 vue 一样,使用 if 循环出来,因为在 react 中的 jsx 的 js 语法要写在 {} 花括号中,这样使用 if 循环,就很难写;

我们可以使用 map 来进行渲染,然后要写在 {} 花括号里面

{
    this.state.list.map((item, index) => {
      return <li>{item}</li>
    })
}

2. key

如果在循环中,没有写 key,会报错的,所以呢!

要注意 react 和 vue 一样,需要 key ,然后不能单单使用 index 来作为 kye 的值,因为页面中可能有多个循环,就有多个相同的 key 值了啊,懂了吗?

所以,我们将 item + index 组成 key 的值 。

key={item+index}

以下代码:

class Naruto extends Component {
  // 数据
  constructor (props) {
    super(props)    // 调用父类的构造函数,固定写法
    this.state = {
      inputValue: '',
      list: ['隐分身', '螺旋丸']
    }
  }
  // 结构
  render () {
    return (
      <Fragment>
        <input value={this.state.inputValue} onChange={this.inputChange.bind(this)}/>
        <button onClick={this.addList.bind(this)}>学习忍术</button>

        <ul>
          {
            this.state.list.map((item, index) => {
              return <li key={item+index}>{item}</li>
            })
          }
        </ul>
      </Fragment>
    )
  }
}

ey` 值了啊,懂了吗?

所以,我们将 item + index 组成 key 的值 。

key={item+index}

以下代码:

class Naruto extends Component {
  // 数据
  constructor (props) {
    super(props)    // 调用父类的构造函数,固定写法
    this.state = {
      inputValue: '',
      list: ['隐分身', '螺旋丸']
    }
  }
  // 结构
  render () {
    return (
      <Fragment>
        <input value={this.state.inputValue} onChange={this.inputChange.bind(this)}/>
        <button onClick={this.addList.bind(this)}>学习忍术</button>

        <ul>
          {
            this.state.list.map((item, index) => {
              return <li key={item+index}>{item}</li>
            })
          }
        </ul>
      </Fragment>
    )
  }
}

7. 插件

1. React 快速生成语法插件

Simple React Snippets 为 vscode 中的插件,以下是快速生成的语法:

1,cc 快速生成 组件的结构;

2,imrc 导入 React 的基本文件;

SnippetRenders
imrImport React
imrcImport React / Component
imrsImport React / useState
imrseImport React / useState useEffect
imptImport PropTypes
impcImport React / PureComponent
ccClass Component
cccClass Component With Constructor
cpcClass Pure Component
sfcStateless Function Component
cdmcomponentDidMount
uefuseEffect Hook
cwmcomponentWillMount
cwrpcomponentWillReceiveProps
gdsgetDerivedStateFromProps
scushouldComponentUpdate
cwucomponentWillUpdate
cducomponentDidUpdate
cwucomponentWillUpdate
cdccomponentDidCatch
gsbugetSnapshotBeforeUpdate
sssetState
ssfFunctional setState
usfDeclare a new state variable using State Hook
renrender
rpropRender Prop
hocHigher Order Component

8. 组件的拆分

1. 创建组件

创建 js 文件,然后导出,注意创建的组件文件必须首字母大写哦 !

// 列表组件
import React, { Component } from 'react';

class Lists extends Component {
  render() { 
    return ( 
      <li>你好</li>
     )
  }
}
 
export default Lists;

2. 导入组件

在页面上导入组件,然后根据导入的组件,在页面填写 组件标签;
关键代码:

<Lists />

全部代码:

// 鸣人组件
import React, { Component, Fragment } from 'react'
import './App.css'
import Lists from './Lists'

class Naruto extends Component {
  // 数据
  constructor (props) {
    super(props)    // 调用父类的构造函数,固定写法
    this.state = {
      inputValue: '',
      list: ['隐分身', '螺旋丸']
    }
  }
  // jsx 语法 ( 模板
  render () {
    return (
      <Fragment>
        <label htmlFor="inputId">添加学习忍术:</label>
        <input id="inputId" value={this.state.inputValue} onChange={this.inputChange.bind(this)}/>
        <button onClick={this.addList.bind(this)}>学习忍术</button>
        <ul>
          {
            this.state.list.map((item, index) => {
              return (
                <Lists key={item + index} />
              ) 
            })
          }
        </ul>
      </Fragment>
    )
  }
}

export default Naruto


9. 父子组件的传值

1. 自定义属性的方法:

在子组件中可以使用

this.props.父组件传递的属性名

来获取父组件传递过来的值;



成员: 父组件(Naruto.js), 子组件(items.js);

界面是这样子的,我们把界面分为2个组件

在这里插入图片描述

1. 父向子传值

1,父组件要传递列表的内容给子组件,让子组件渲染内容;

2,在父组件中引入 子组件;

Naruto.js
// 鸣人组件
import React, { Component, Fragment } from 'react'
import Items from './Items'

3, 就可以对应的写上 子组件的标签名了,和引入名称一致;

 <Items />

4, 使用 自定义属性 传递数据给子组件;

<ul>
  {
    // 循环渲染内容
    this.state.list.map((item, index) => {
      return (
        // 使用组件
        <Items 
          key={item + index}
          // 传递 每项的内容 给子组件
          content={item}
         />
      ) 
    })
  }
</ul>

自定义属性的值,就是传递给子组件的数据,我们在子组件中获取数据;

5, 子组件 获取父组件传递的数据 this.props.xxx

<li>{this.props.content}</li>

就可以了。


2. 子向父传值

其实就是,子组件调用父组件的方法;

实现:点击子组件中的 li 要删除 父组件 中对应的数据;

1,在 子组件 的 li 绑定事件,让事件顺利触发:

Item.js
import React, { Component, Fragment } from 'react';

class Item extends Component {
  render() { 
    return ( 
      <Fragment>
        <li onClick={this.delete.bind(this)}>
            {this.props.content}
        </li>
      </Fragment>
     )
  }
  delete () {
   	console.log(1)
  }
}

2,在父组件 中传递 删除 li 的函数给 子组件:

<ul>
  {
    this.state.list.map((item, index) => {
      return (
        <Lists 
          key={item + index}
          content={item}
          // 传递索引
          index={index}
          // 传递删除事件
          deleteList={this.deleteList.bind(this)}
         />
       ) 
    })
  }
</ul>

3,子元素在 li 的点击函数中调用传递过来的事件:

Items.js

class Lists extends Component {
  render() { 
    return ( 
      <Fragment>
        <li onClick={this.delete.bind(this)}>{this.props.content}</li>
      </Fragment>
     )
  }
  delete () {
    // 接收父组件传递过来的函数,并传入当前点击的 li 的索引, index 是父组件传递过来的;
    this.props.deleteList(this.props.index)
  }
}

10. React 的特性

1. 单向数据流

父组件 传递给 子组件 的数据,在子组件中不能修改 父元素传递过来的数据;只能读取数据;


2. 搭配 jq

React 只对 index.html 中 id 为 rootdiv 起作用;

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <title>React 例子</title>
  </head>
  <body>
    <!-- js 文件出错或为加载完毕,就会提示这句话,可以加强程序的健壮性 -->
    <noscript>js 文件出错</noscript>
    <div id="root"></div>
    <div>我在 react 的控制范围外</div>
  </body>
</html>


在这里插入图片描述


3. 函数式编程

函数式编程的优点:

1,可以让代码更清晰,每一个功能都是一个函数;

2,更方便 代码的测试,容易实现 前端 自动化测试;


11. 调试工具

React developer tools

React developer tools有三种颜色,三种颜色代表三种状态:

  1. 灰色: 这种就是不可以使用,说明页面不是又React编写的。
  2. 黑色: 说明页面是用React编写的,并且处于生成环境当中。
  3. 红色: 说明页面是用React编写的,并且处于调试环境当中。

12. PropTypes 校验传递值

在 React 中父子组件传值时,给 传递的数据 进行校验,可以减少 bug 的出现,我们可以使用 PropTypes 来进行校验 。


1. 引入 prop-types

import PropTypes from 'prop-types'

2. 添加校验规则:

注意:要写在 class 外面 。

class Lists extends Component {
	..........
}

// 校验:在 class 外,进行校验, 校验从父组件传递过来的数据
Lists.propTypes = {
  接收数据: PropTypes.数据类型,
  content: PropTypes.string
}

注意大小写:

Lists.propTypes = {   // 小写
  接收数据: PropTypes.数据类型,  // 大写
  content: PropTypes.string
}

例子:

// 列表组件
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'

class Lists extends Component {
  render() { 
    return ( 
      <Fragment>
          {this.props.date} + {this.props.content}
      </Fragment>
     )
  }
}

// 校验:在 class 外,进行校验, 校验从父组件传递过来的数据
Lists.propTypes = {
  content: PropTypes.string,
  index: PropTypes.number,
  deleteList: PropTypes.func,
  date: PropTypes.string.isRequired,
}
 
export default Lists;

更多的规则可以查看 查看更多


常用规则

  • 接收数据: PropTypes.数据类型

    • 数据类型支持: 常见的类型 string, number, func, bool, object, symbol
  • 是否必传: 接收数据: PropTypes.数据类型.isRequired

    • 默认为否;

3. 添加默认值:

设置如果传递的参数,没有传递数据进来,就默认输出这个值;

类名.defaultProps = {
    接收数据: 'xxx'
}

例子:

class Lists extends Component {
  render() { 
    return ( 
      <Fragment>
        {this.props.level}
      </Fragment>
     )
  }

// 默认值
Lists.defaultProps = {
  level: '高级'
}

13. React 的 ref

1. 基本使用:

React 的 ref 他和 vue 中的 ref 有点不一样;虽然他可以获取 页面上的 DOM 元素;但操作方法不一样;

React 的 ref

1,在标签上使用 ref 标记元素

<input 
  value={this.state.inputValue} 
  onChange={this.inputChange.bind(this)}
  ref={(input) => { this.input = input }}
/>

使用 ref 来传递标签元素,在使用箭头函数中给 this 添加一个属性,

属性值就为 标签名,就是 标签元素(DOM);

2,在函数中可以获取该元素

inputChange () {
    // 获取 ref 的值
    console.log(this.input);
}

在控制台中输出为:

在这里插入图片描述


作用:

可以更语义化的表明操作的值

// 使用前  
inputChange (e) {
    // 获取 input 中输入的数据
    const data = e.target.value
    // 赋值到 this.start 中
    this.setState({
      inputValue: data
    })
}  


// 使用后
inputChange () {
    // 获取 ref 中的 input
    const data = this.input.value
    // 赋值到 this.start 中
    this.setState({
      inputValue: data
    })
}

Vue 的 ref

<input ref="inputRef"/>

<script>
	new Vue({
        data: {
            return {}
        },
        methods: {
        	fn () {
        		// 输出 input 这个 DOM 元素
        		console.log(this.$refs.inputRef);
    		}    
        }
    })
</script>

2. 坑

如果我们想要在修改 数据后,进行某些操作,比如

我们想要点击按钮时,输出 子节点的数量(包括现在添加的子节点)

在这里插入图片描述

我们使用 ref 来获取 ul 中的 li 个数;

1,在 ul 标签使用 ref 标记;

2,在点击按钮中,输出 ul 中的 li 个数;

// 鸣人组件
import React, { Component, Fragment } from 'react'
import './App.css'
import Lists from './Lists'

class Naruto extends Component {
  // 数据
  constructor (props) {
    super(props)    // 调用父类的构造函数,固定写法
    this.state = {
      inputValue: '',
      list: ['隐分身', '螺旋丸']
    }
  }
  // jsx 语法 ( 模板
  render () {
    return (
      <Fragment>
        <label htmlFor="inputId">添加学习忍术:</label>
        <input 
          id="inputId" 
          value={this.state.inputValue} 
          onChange={this.inputChange.bind(this)}
          ref={(input) => { this.input = input }}
        />
        <button onClick={this.addList.bind(this)}>学习忍术</button>
        <ul ref={(ul) => { this.ul = ul }}>
          {
            this.state.list.map((item, index) => {
              return (
                <Lists 
                  key={item + index}
                  content={item}
                  index={index}
                  deleteList={this.deleteList.bind(this)}
                  date={'4个月'}
                   />
              ) 
            })
          }
        </ul>
      </Fragment>
    )
  }
  inputChange () {
    // 获取 ref 的值
    const data = this.input.value
    // 赋值到 this.start 中
    this.setState({
      inputValue: data
    })
  }
  // 点击按钮添加数据
  addList () {
    // 修改数据
    this.setState({
      list: [...this.state.list, this.state.inputValue],
      inputValue: ''
    })
      
    // 使用 ref 来获取 ul 的子元素数量(主要是这个)
    console.log(this.ul.querySelectorAll('li').length)
  }
}

export default Naruto

3,出问题了,点击输出的 li 个数为之前的个数,不包含 新添加的 li

4,这个是因为 this.setState 操作异步的,所以在 this.setState 还没有执行时, consle.log() 就输出了。

5,在 tis.setState 有一个回调函数,在 修改数据后,才执行,就可以顺利输出;

  // 点击按钮添加数据
  addList () {
    this.setState({
      list: [...this.state.list, this.state.inputValue],
      inputValue: ''
    }, () => {
      console.log(this.ul.querySelectorAll('li').length)
    })
  }

14. React 组件的生命周期

因为 React 中的所有文件都是 组件,所有在所有 文件中,都适用;

现在时间为 2020.12.23

官网文档位置: API REFERENCE > React > React.Component


语法废用:

React16新的生命周期还没有弃用,17 就不用了

componentWillMount、

componentWillReceivePorps,

componentWillUpdate

新增了

getDerivedStateFromProps、

getSnapshotBeforeUpdate

来代替弃用的三个钩子函数



生命周期图:

首先 框架的语法,都会随着版本的修改,而发送改变,以下的图是 React ^16.4 版本的:

在这里插入图片描述

声明周期的 3 个阶段:

  • 挂载阶段
    • constructor() 构造函数;
    • static getDerivedStateFromProps()
    • render() 初始化 state 中的数据;
    • componentDidMount() 完成 DOM 的挂载;
  • 更新阶段
    • static getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render() 当 state 或 props 数据发送变化触发;
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()
  • 销毁阶段

1. 挂载阶段

constructor()

概念

在 React 组件挂载之前,会调用它的构造函数。 组件中的数据 state 或 propo 发送变化时会再次执行 。


语法: (完整写法)

constructor(props) {
    // 获取父组件传递的数据
    super(props)
    // 初始化内部的 state(数据), vue 中的 data
    this.state = {
        xxx: xxx
    }
    // 修改组件中函数的 this 指向
    this.xxx = this.xxx.bind(this)
}

如果 不初始化 state 或不进行方法的绑定,则不需要为 React 组件实现 comstructor 构造函数;(就是如果组件中 不写 this.state 进行管理数据 和 不用进行函数的 this 指向修改,就可以不写这个生命周期函数,但是要接收父组件的数据还是要写的)。


分析

super(props) : 如果不写他,在组件中使用 this.props 获取不到父组件传递的数据;


注意

修改 this.state 中的数据,需要调用 this.setState() 方法;

获取 this.state 中的数据,只需调用 this.state.xxx 来获取数据;

避免将 props 的值 赋给 this.state 中的数据;

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

static getDerivedStateFromProps()

概念

​ 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。


语法

static getDerivedStateFromProps(props, state) {
    // props: 父组件传递的数据
    // state: this.state 中的数据
    return state;  // 返回值必须要有值
}

componentDidMount()

概念

​ 在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。


语法

componentDidMount() {
    // dom 节点已进行了挂载
    // 发送网络请求
}

render()

概念

stateprops 更新时触发render() 方法是 class 组件中唯一必须实现的方法。

render 被调用时,它会检查 this.propsthis.state 的变化并返回以下类型之一:

  • React 元素;
  • 数组 或 fragments;
  • Portals;
  • 字符串 或 数值类型;
  • 布尔值类型 或 null;

语法

render () {
    return (
    	<Fragment>
        	xxxxxxxxx
        </Fragment>
    )
}

注意

​ 如果 shouldComponentUpdate() 返回 false,则不会调用 render()



2. 更新阶段

shouldComponentUpdate()

概念

​ 函数会在组件更新之前,自动被执行。

他可以 根据返回返回值:

  • true,就同意组件更新;
  • false,就反对组件更新。

语法

shouldComponentUpdate(nextProps, nextState) {
    xxxxx
    return true / false
}

注意

  • 它要求返回一个布尔类型的结果,必须有返回值,这里就直接返回一个true了(真实开发中,这个是有大作用的)。

使用

​ 父组件的数据改变,会导致 子组件 在没有必要的情况下从新加载页面,所以可以使用 该周期函数,进行判断,看看子组件是否要进行更新;(写在子组件中),但目前判断的结果是 一致的,就是不管怎么弄, nextPropsthis.props 的数据是一样的,导致无法判断;

  shouldComponentUpdate (nextProps, nextState, nextContent) {
    console.log(nextProps)
    console.log(this.props)
    if (nextProps.content !== this.props.content) {
      console.log('不一样')
      return true;
    }
    console.log('一样')
    return false
  }



getSnapshotBeforeUpdate()

概念

​ 组件的值发送变化后,但还没有更新到 DOM 之前触发,他可以 从数据中获取以下信息,传递给下一个生命周期 componentDidUpdate()

语法

getSnapshotBeforeUpdate(prevProps, prevState) {
    // prevProps 父组件传递的值
    // prevState 组件state 中的值
    return snapshot / null;
}

返回的值在 componentDidUpdata 生命周期中的第三个参数,可以获取到;


componentDidUpdate()

概念

​ 在组件更新之后执行,它是组件更新的最后一个环节。首次渲染不会执行此方法。


语法

componentDidUpdate(prevProps, prevState, snapshot) {
    // prevProps: 父组件传递的值
    // prevState: 组件的数据
    // snapshot: 上个生命周期传递过来的值
}

注意

​ 如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined 。



3. 销毁阶段

componentWillUnmount()

概念

​ 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,

​ 这个函数时组件从页面中删除的时候执行,例如我们删除组件中的内容时,会触发


语法

componentWillUnmount () {
    xxx
}



15. 动画钩子

1. 下载

React 和 Vue 一样,提供了一系列的 动画钩子函数,可以实现 进出场动画;


下载

npm install react-transition-group --save

组成

  • Transition
  • CSSTransition : css 动画钩子函数
  • SwitchTransition
  • TransitionGroup

步骤

1,导入需要的包;

2,写必要的标签,包裹需要实现动画的元素;

3,写需要的参数;

4,书写 css 样式;



CSSTransition 参数介绍

in开始动画的开关,一般为某个控制动画的参数
timeout实现动画的时间(毫秒)
classNames动画钩子函数的开头名字 (.xx-enter-done
key当实现循环中的元素的动画时,就需要写 key 值;



动画钩子函数

.xx-enter入场前
.xx-enter-active入场时
.xx-enter-done入场后
.xx-exit出场前
.xx-exit-active出场时
.xx-exit-done出场后

如果不设置 出场后 的样式,动画会结束后会变成 开始的样式;不能保存出场的状态;



2. 单元素使用

单个元素使用,只要导入 CSSTransition 就可以了

Move.js

import React, { Component, Fragment } from 'react'
// 1. 导入所需的文件
import { CSSTransition } from 'react-transition-group'
import './App.css'

class Move extends Component {
  constructor (props) {
    super(props)
    this.state = {
      isShow: true
    }
    this.moveInfo = this.moveInfo.bind(this)
  }

  render () {
    return (
         <Fragment>
          // 2. 使用标签包裹
          <CSSTransition
            in={this.state.isShow}
            timeout={2000}
            classNames="move">
            <div>
              我有动画效果
            </div>
          </CSSTransition>
          <input onClick={this.moveInfo} type="button" value="我是按钮" />
        </Fragment>
    )
  }
  moveInfo () {
    let isShow = this.state.isShow
    this.setState({
      isShow: !isShow
    })
  }
}

export default Move


3,动画钩子函数

App.css
/* 入场前 */
.move-enter { opacity: 0; }
/* 入场时 */
.move-enter-active { opacity: 1; transition: opacity 2s; }
/* 入场后 */
.move-enter-done { opacity: 1; }

.move-exit { opacity: 1; }
.move-exit-active { opacity: 0; transition: opacity 2s; }
.move-exit-done { opacity: 0; }



3. 多个元素使用

多个元素就要导入 TransitionGroup(包裹整个元素) 和 CSSTransition(包裹单个元素)了。

Naruto.js

import React, { Component, Fragment } from 'react'
import './App.css'
import Lists from './Lists'
import Move from './Move'
// 1. 导入包
import { CSSTransition, TransitionGroup } from 'react-transition-group'

class Naruto extends Component {
  // 数据
  constructor (props) {
    super(props)    // 调用父类的构造函数,固定写法
    this.state = {
      inputValue: '',
      list: []
    }
  }
  // jsx 语法 ( 模板
  render () {
    return (
      <Fragment>
        <label htmlFor="inputId">添加学习忍术:</label>
        <input 
          id="inputId" 
          value={this.state.inputValue} 
          onChange={this.inputChange.bind(this)}
          ref={(input) => { this.input = input }}
        />
        <button onClick={this.addList.bind(this)}>学习忍术</button>
        <ul ref={(ul) => { this.ul = ul }}>
           // 2. 使用标签包裹整个实现动画的元素
          <TransitionGroup>
            {
              this.state.list.map((item, index) => {
                return (
                  // 3. 使用标签,包裹实现动画的元素,并设置参数
                  <CSSTransition 
                    timeout={2000} 
                    classNames="move"
                    key={item + index}>
                    <Lists 
                      key={item + index}
                      content={item}
                      index={index}
                      deleteList={this.deleteList.bind(this)}
                      date={'4个月'}
                      />
                  </CSSTransition>
                ) 
              })
            }
          </TransitionGroup>
        </ul>
        <Move />
      </Fragment>
    )
  }
  inputChange () {
    // 获取 ref 的值
    const data = this.input.value
    // 赋值到 this.start 中
    this.setState({
      inputValue: data
    })
  }
  // 点击按钮添加数据
  async addList () {
    // 如果为空,提醒添加内容
    if (!this.state.inputValue) {
      return alert('请输入内容')
    }
    await this.setState({
      list: [...this.state.list, this.state.inputValue],
      inputValue: ''
    })
  }
  // 点击删除
  deleteList (index) {
    // react 不能直接修改 state 中的数据,要拿出来后,再进行修改,在赋值进去
    // 获取值
    const lists = this.state.list;
    // 修改值
    lists.splice(index, 1);
    // 把处理后的值,放到 state 中
    this.setState({
      list: lists
    })
  }
}

export default Naruto

4,设置动画钩子函数,和 List.js 是一样的,就不写了。


注意

这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅 。


学习视频:技术胖哔哩哔哩视频


Redux

1. 基础:

概念

Redux 是一个用来 管理管理数据状态UI状态 的JavaScript 应用工具。随着JavaScript单页应用(SPA)开发日趋复杂,JavaScript需要管理比任何时候都要多的state(状态),Redux就是降低管理难度的。


工作流程

图片来自 技术胖 的视频中:
在这里插入图片描述

React Components: 组件;

ActionCreaturs: 操作数据的管理人员;

Store: 数据存放中心;

Reducers:数据管理 / 查找中心;


2. 创建一个基础的 Redux 仓库

安装 Redux

npm i --save redux

创建 store 仓库

在项目 src 文件夹下创建一个 store 的文件夹,存放 redux 相关的文件;

index.js (项目的 store 文件)是文件夹的入口文件:

src > store > index.js

// 导入 redux 方法
import { createStore } from 'redux'

// 创建数据存储的仓库
const store = createStore()

export default store

创建 reducer 管理功能模块

概述:

​ 他有管理 store 中数据的功能,但只是的功能模块,需要有人去使用它;

在 store文件夹 下创建 reducer.js 文件:

src > store > reducer.js

// 默认数据
const defaultState = {}

// state 是项目管理的数据信息,一开始没有,使用一个默认值
const fn = (state = defaultState, action) => {
    return state
}

/*
  如果直接暴露个箭头函数,会出现以下警告,所以我改成把 箭头函数赋给变量,然后导出变量  
  Assign arrow function to a variable before exporting as module default  import/no-anonymous-default-export
*/

export default fn

将 reducer 导入 store 中,进行挂载

src > store > index.js

// 导入 redux 方法
import { createStore } from 'redux'

// 导入 reducer.js
import reducer from './reducer'

// 创建数据存储的仓库
const store = createStore(reducer)

export default store

在 组件中获取数据

存放数据:我们把数据存放到 reducer.js 中,然后在组件中获取:

src > store > reducer.js

// 管理 redux 功能模块,有管理数据的功能

// 默认数据
const defaultState = {
  inputValue: '请输入内容',
  list: [
    '学习技术胖的Redux视频啊',
    '看面试题,看到 作用域链 和 闭包'
  ]
}

// 暴露方法函数
const fn = (state = defaultState, action) => {
  // state 是整个项目中需要管理的数据,暂时没有数据,用 空对象 代替
  return state
}

export default fn

获取数据:然后在 组件 中导入 store 文件:

src > App.js

import React, { Component, Fragment } from 'react';
import './App.css'
import 'antd/dist/antd.css'
import { Button, Input, List } from 'antd'
// 1. 导入
import store from './store/index'

class App extends Component {
  constructor (props) {
    super(props)
    // 2. 赋值给 this.state
    this.state = store.getState()
  }
  render() { 
    return ( 
      <Fragment>
        <div className="content">
          <div className="header">
            <Input placeholder={this.state.inputValue} />
            <Button type="primary">添加任务</Button>
          </div>
          <div className="list">
            <List
              bordered
              dataSource={this.state.list}
              renderItem={item => (
                <List.Item>
                  {item}
                </List.Item>
              )}
            />
          </div>
        </div>
      </Fragment>
     );
  }
}
 
export default App

在控制台打印出来为:

在这里插入图片描述


总结

  • 创建 store 和 reducer 文件;
  • 然后在 store 中挂载 reducer;(挂载功能)
  • 然后在 reducer 中存放数据;
  • 在 组件 中导入文件;
  • 然后获取数据 store.getState()

谷歌浏览器 Redux DevTools 插件:

可以看到项目中的 redux ,安装后,要进行配置,把

window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

复制到项目中的 store 文件中

src > store > index.js

// redux 数据存放中心
import { createStore } from 'redux'
// 导入 reducer 把他传入 store 中
import reducer from './reducer'

// 创建数据存储仓库
const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )

export default store

3. 修改 Redux 中的数据

上面我们在 组件 中获取到 redux 中的数据,现在我们要通过 组件 修改 redux 中的数据:


1. 组件发送数据到 store 中:

1,给 组件 绑定事件,获取 input 输入框的数据,然后发送 数据给 store

我们创建了一个 管理人员 action 来携带数据给 store

class App extends Component {
    constructor (props) {
        
    }
    
    render() {
        <Fragment>
        	<Input
              placeholder={this.state.inputValue}
              onChange={this.changeInputValue} 
             />
        </Fragment>
    }
    changeInputValue (e) {
         // 获取 input 中的字符串
         const data = e.target.value
         // 1. 创建Action, store 的管理人员
         const action = {
           type: 'changeInputValue',  // action 的名字,标记数据
           value: data
         }
         // 2. 发送数据到 store 中
         store.dispatch(action)
    }
}

最好是要设置一个 防抖设置,因为你在 input 中每点击键盘就会触发事件,进行操作 store

这样会很耗费性能的,所以我们可以在 change 事件中设置一个定时器,每次触发事件 change时,先清空定时器,带从新开启一个定时器 。


2. store 自动推送数据到 reducer 中:

store 只是一个仓库,他并没有管理能力,他会把接收到的 state 数据自动转发给 Reducer

我们可以在 Reducer 中输出看看:

src > store > reducer.js

const fn = (state = defaultState, action) => {
  /* 
  	state: 指的是原始仓库里的状态。
    action:指的是 action 新传递的状态 */
    console.log(state)
    console.log(action)
    
    return state
}

export default fn

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0pWWQmtX-1609134648795)(H:\笔记本\images\React\red 中输入数据.png)]

注意记住:reducer 里只能接收 state,不能改变 state 。

所以,我们接收到数据后,不对原数据进行修改,我们可以 拷贝一份,然后进行修改:

src > store > reducer.js

const fn = (state = defaultState, action) => {
  /* store 没有处理数据的功能,他会自动转发到 reducer 中。
    注意:Reducer里只能接收state,不能改变state。)  */
  // 判断接收的类型是否是我们传递过来的类型
  if (action.type === 'changeInputValue') {
    // 把原始数据拷贝一份
    let newState = JSON.parse(JSON.stringify(state))
    // 修改拷贝数据的值 = 传递 input 中的值
    newState.inputValue = action.value
    
    return newState
  }
  return state
}

3. 组件获取新的 store 数据:

在组件中创建一个函数,获取 store 中的数据,使用 store.subscribe(函数名) 触发函数;就像 vue 中的 订阅消息,然后等待 消息发布,就会触发;

src > store > App.js

1,创建函数

  storeChange () {
    // 修改组件中的数据
    this.setState(store.getState())
  }

2,进行消息的订阅

constructor (props) {
    super(props)
    this.setState = this.setState.bind(this)
    // 订阅
    store.subscribe(this.setState)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值