1 Star 4 Fork 6

code2332/张天禹React基础学习记录

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
2.React_staging @ 9ae72ed
3年前
3.Redux_demo @ 4737db7
3年前
4.react_extension @ 5310682
3年前
5.hello_router6_demo @ b4231e0
3年前
Loading...
README

React笔记

相关资源

FrontEndNotes笔记 原课程链接 大佬仓库 React 中文官网

前言 | Reat简介

React:用于构建用户界面的 JavaScript 库。由 Facebook 开发且开源。是一个将视图渲染为html视图的开源库

为何学习 React

原生 JavaScript 的痛点:

  • 操作 DOM 繁琐、效率低
  • 使用 JavaScript 直接操作 DOM,浏览器进行大量重绘重排
  • 原生 JavaScript 没有组件化编码方案,代码复用率低

React 的特点:

  • 采用组件化模式、声明式编码,提高开发效率和组件复用率
  • 在 React Native 中可用 React 语法进行移动端开发
  • 使用虚拟 DOM 和 Diffing 算法,减少与真实 DOM 的交互(数据-虚拟DOM-真实DOM)

需要掌握的js基础

判断this指向

class类

ES6语法规范

npm包管理

原型以及原型链

数组常用方法

模块化

第一章:React入门

Hello React

相关js库

  • react.development.js :React 核心库
  • react-dom.development.js :提供 DOM 操作的 React 扩展库
  • babel.min.js :解析 JSX 语法(js语法糖),转换为 JS 代码
    <!-- 准备好一个“容器” -->
    <div id="test"></div>

    <!-- 引入react核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <!-- 此处一定要写babel,表示写的不是 JS,而是 JSX,并且靠 babel 翻译 -->
    <script type="text/babel">
      //1.创建虚拟DOM 此处是JSX 不要写引号,因为不是字符串
      const VDOM = <h1>Hello,React</h1>

      //2.渲染虚拟DOM到页面
      // 导入核心库和扩展库后,会有 React 和 ReactDOM 两个对象
      ReactDOM.render(VDOM, document.getElementById('test'))
    </script>

创建VDOM的两种方式

第一种 jsx方式(推荐)

    <div id="test"></div>
    
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>

    <script type="text/babel">
      //1.创建虚拟DOM 
      const VDOM = (
        <h1>
            <span>Hello,React</span>
        </h1>
      )
      //2.渲染虚拟DOM到页面
      ReactDOM.render(VDOM, document.getElementById('test'))
    </script>

第二种方式

    <div id="test"></div>

    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>

    <script type="text/javascript">
      //1.创建虚拟DOM
      const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hell,React'))

      //2.渲染虚拟DOM到页面
      ReactDOM.render(VDOM, document.getElementById('test'))
    </script>

VDOM | DOM

关于虚拟 DOM:

  • 本质是 Object 类型的对象(一般对象)
  • 虚拟 DOM 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多的属性。
  • 虚拟 DOM 最终会被 React 转化为真实 DOM,呈现在页面上。
    <script type="text/babel">
      //1.创建虚拟DOM 
      const VDOM = (
        <h1>
            <span>Hello,React</span>
        </h1>
      )
      //2.渲染虚拟DOM到页面
      ReactDOM.render(VDOM, document.getElementById('test'))

      console.log('VDOM',VDOM); // VDOM {...}
      console.log('TDOM',document.querySelect('#test')); // TDOM <div>...</div>
      console.log(typeof VDOM); // object
      console.log(VDOM instanceof Object); // true
    </script>

React JSX

全称:JavaScript XML React 定义的类似于 XML 的 JS 扩展语法;本质是 React.createElement() 方法的语法糖 作用:简化创建虚拟 DOM 补充:js中,JSON的序列化和反序列化使用parse()/stringify() JSX 语法规则

  • 定义虚拟 DOM 时,不要写引号
  • 标签中混入 JS 表达式需要使用 {}
  • 指定类名不用 class,使用 className
  • 内联样式,使用 style={{ key: value }} 的形式
  • 只能有一个根标签
  • 标签必须闭合,单标签结尾必须添加 /:<input type="text" />
  • 标签首字母小写,则把标签转换为 HTML 对应的标签,若没有,则报错
  • 标签首字母大写,则渲染对应组件,若没有定义组件,则报错
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>jsx语法规则</title>
    <style>
      .title {
        background-color: orange;
        width: 200px;
      }
    </style>
  </head>
  <body>
    <div id="test"></div>
    ...
    <script type="text/babel">
      const myId = 'aTgUiGu'
      const myData = 'HeLlo,rEaCt'

      const VDOM = (
        <div>
          <h2 className="title" id={myId.toLowerCase()}>
            <span style={{ color: 'white', fontSize: '19px' }}>{myData.toLowerCase()}</span>
          </h2>
          <input type="text" />
          // <good>very good</good>
          // <Child></Child>
        </div>
      )

      ReactDOM.render(VDOM, document.getElementById('test'))
    </script>
  </body>
</html>

补充:表达式与语句(代码)的区别

  • 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方(如数值处理等)
   a
   a+b
   demo(1)
   arr.map()
   function test () {}
  • 语句(代码),下面这些都是语句(如逻辑判断语句)
    if(){}
    for(){}
    switch(){case:xxxx}

第二章:React面向组件

创建组件的两种方式

函数式组件

    <div id="app"></div>

    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    
    <script type="text/babel">
        // 创建组件
        function Com() {
            console.log(this) //babel编译后开启严格模式 此处的this是undefind
            return <h1>函数式组件,适用于简单组建的创建</h1>
        }
        // 挂载组件
        ReactDOM.render(<Com/>,document.querySelector("#app"))
    </script>

要点:

  • 组件名称首字母必须大写,否则会解析成普通标签导致报错(JSX 语法规则)
  • 函数需返回一个虚拟 DOM
  • 渲染组件时需要使用标签形式,同时标签必须闭合

渲染组件的过程:

  • React 解析标签,寻找对应组件
  • 发现组件是函数式组件,则调用函数,将返回的虚拟 DOM 转换为真实 DOM ,并渲染到页面中

类组件

类的基本知识

    <script type="text/javascript">
        /*
            总结:
            1. 类的实例不是必须写的,需要对类进行初始化操作,如添加指定属性时才写
            2. 子类继承父类,子类中写了构造器,那么子类构造器中的super是必须要调用的
            3. 类中定义的方法都是放在了类的原型对象上,供实例去使用
            4. 实例调用子类的方法时,找不到会去父类的原型对象上去找,一直找到顶级window对象
        */
        class Person {
            // 构造器方法
            constructor(name,age) {
                // 构造器中的this指向类的实例对象(constructor是一个函数,由实例对象调用,所以this指向实例对象)
                this.name = name
                this.age = age
            }
            // 一般方法
            speak() {
                // 放在了类的原型对象上,供实例使用
                // 通过Person实例调用speak方法时,speak中的this指向Person实例
                console.log(`我的名字叫${this.name},今年${this.age}岁了`);
            }
        }
        // Student类继承与Person类
        class Student extends Person {
            constructor(name,age,id) {
                super(name,age)
                this.id = id
                this.school = "bilibili"
            }
            // 重写从父类继承的方法
            speak() {
                console.log(`我的名字叫${this.name},今年${this.age}岁了,编号为${this.id}`);
            }
        }
        const p1 = new Person('张三',18)
        p1.speak()
        const s1 = new Student('李四',16,'30461')
        s1.speak()
    </script>

组件渲染过程:

  1. React 解析组件标签,寻找组件
  2. 发现是类式组件,则 new 该类的实例对象,通过实例调用原型上的 render 方法
  3. 将 render 返回的虚拟 DOM 转为真实 DOM ,渲染到页面上

组件实例的核心属性

核心属性1:state | 状态

state 是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state 来更新对应的页面显示。 要点:

  • 初始化 state
  • React 中事件绑定
  • this 指向问题
  • setState 修改 state 状态
  • constructor 、render 、自定义方法的调用次数
  1. 标准写法:
    <script type="text/babel">
        class Weather extends React.Component {
            constructor(props) {
                super(props) 
                // 初始化状态
                this.state = {isHot:true}
                // 解决changeWeather中的this指向问题
                this.change = this.changeWeather.bind(this)
                
            }
            render() {
                // 读取状态
                const {isHot} = this.state 
                // 这里的this指向原型对象
                // return <h1 onClick={this.changeWeather}>今天天气{isHot?'凉爽':'炎热'}</h1>

                // 这里的this指向实例自身
                return <h1 onClick={this.change}>今天天气{isHot ? '1' :'2' }</h1>
            }
            changeWeather() {
                // changeWeather放在实例对象的原型上,供实例使用
                // changeWeather作为onClick的回调函数使用,不是通过实例调用,是直接调用
                // 类中的方法默认开启了严格模式,this指向丢失

                // 状态不可直接更改,必须借助React内置API
                // this.state.isHot = !this.state.isHot
                this.setState({isHot:!this.state.isHot})
            }
        }

        function demo() {
            // 修改状态
        }
        ReactDOM.render(<Weather/>,document.querySelector('#app'))
    </script>
  1. 简写:
    <script type="text/babel">
        // 创建组件
        class Weather extends React.Component {
            // 初始化状态
            state = {isHot:true}

            render() {
                // 读取状态
                const {isHot} = this.state 
                return <h1 onClick={this.changeWeather}>今天天气{isHot ? '炎热' :'凉爽' }</h1>
            }

            // 自定义方法 -- 赋值语句+箭头函数(中的this指向上下文中的this)
            changeWeather = ()=>{
                this.setState({isHot:!this.state.isHot})
            }
        }
        ReactDOM.render(<Weather/>,document.querySelector('#app'))
    </script>

核心属性2:props | 标签属性

每个组件对象都有 props 属性,组件标签的属性都保存在 props 中。(注意:props 是只读的,不能修改。) 要点:

  • 展开运算符解构赋值
  • props数据采用标签属性的方式传值
  • 批量传递标签属性
  1. 标准写法:
    <script type="text/babel">
        // 假数据
        const obj = {name:"张三",age:18,sex:"男"}
        // 创建组件
        class Person extends React.Component {
            render() {
                const {name,age,sex} = this.props
                return (
                    <div>
                        <h1>{name}</h1>
                        <h2>{age}</h2>
                        <h2>{sex}</h2>
                    </div>
                )
            }
        }
        // 渲染组件
        // ReactDOM.render(<Person name="zhangsan" age="18" sex="1"/>,document.querySelector('#app'))
        // 批量传递props/批量传递标签属性
        ReactDOM.render(<Person {...obj}/>,document.querySelector('#app'))
    </script>
  1. 限制标签属性 在 React 15.5 以前,React 身上有一个 PropTypes 属性可直接使用,即 name: React.PropTypes.string.isRequired ,没有把 PropTypes 单独封装为一个模块。 从 React 15.5 开始,把 PropTypes 单独封装为一个模块,需要额外导入使用。
    <div id="app1"></div>
    <div id="app2"></div>
    <div id="app3"></div>

    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 引入prop-type 用于限制props属性标签 -->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    
    <script type="text/babel">
        // 假数据
        const obj = {name:"张三",age:18,sex:"男"}
        // 创建组件
        class Person extends React.Component {
            render() {
                const {name,age,sex} = this.props
                return (
                    <div>
                        <span>{name},</span>
                        <span>{age},</span>
                        <span>{sex}</span>
                    </div>
                )
            }
        }
        // 对标签属性进行限制
        Person.propTypes = {
            name:PropTypes.string.isRequired,//限制name必传且为String类型
            age:PropTypes.number,//限制age为number类型
            sex:PropTypes.string,//限制sex为String类型
            speak:PropTypes.func//限制speak为函数类型
        }
        Person.defaultProps = {
            age:18,//指定age默认值
            sex:'保密'//指定sex默认值
        }

        function speak() {
            console.log('hello');
        }
        // 渲染组件
        ReactDOM.render(<Person name="李四" age={20} sex="男" speak={speak}/>,document.querySelector('#app1'))
        ReactDOM.render(<Person name="李五"/>,document.querySelector('#app3'))
        // 批量传递props/批量传递标签属性
        ReactDOM.render(<Person {...obj}/>,document.querySelector('#app2'))
    </script>
  1. 函数式组件使用props:
    <script type="text/babel">
        // 假数据
        const obj = {name:"张三",age:18,sex:"男"}
        // 创建组件
        function Person(props) {
            const {name,age,sex} = props
            return (
                <div>
                    <h1>{name}</h1>
                    <h2>{age}</h2>
                    <h2>{sex}</h2>
                </div>
            )
        }
        // 对标签属性进行限制
        Person.propTypes = {
            name:PropTypes.string.isRequired,//限制name必传且为String类型
            age:PropTypes.number,//限制age为number类型
            sex:PropTypes.string,//限制sex为String类型
            speak:PropTypes.func//限制speak为函数类型
        }
        Person.defaultProps = {
            age:18,//指定age默认值
            sex:'保密'//指定sex默认值
        }

        ReactDOM.render(<Person {...obj}/>,document.querySelector('#app2'))
    </script>
  1. 简写形式:
    <!-- 引入prop-type 用于限制props属性标签 -->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    
    <script type="text/babel">
        // 假数据
        const obj = {name:"张三",age:18,sex:"男"}
        // 创建组件
        class Person extends React.Component {
            render() {
                const {name,age,sex} = this.props
                return (
                    <div>
                        <span>{name}</span>
                        <span>{age}</span>
                        <span>{sex}</span>
                    </div>
                )
            }
            // 对标签属性进行限制
            static propTypes = {
                name:PropTypes.string.isRequired,//限制name必传且为String类型
                age:PropTypes.number,//限制age为number类型
                sex:PropTypes.string,//限制sex为String类型
                speak:PropTypes.func//限制speak为函数类型
            }
            static defaultProps = {
                age:18,//指定age默认值
                sex:'保密'//指定sex默认值
            }
        }
        

        function speak() {
            console.log('hello');
        }
        // 渲染组件
        ReactDOM.render(<Person name="李四" age={20} sex="男" speak={speak}/>,document.querySelector('#app1'))
        ReactDOM.render(<Person name="李五"/>,document.querySelector('#app3'))
        // 批量传递props/批量传递标签属性
        ReactDOM.render(<Person {...obj}/>,document.querySelector('#app2'))
    </script>

核心属性3:refs | 标识符

通过定义 ref 属性可以给标签添加标识。

  1. 字符串形式的ref(这种形式已过时,效率不高,官方不建议使用。)
<script type="text/babel">
        class Demo extends React.Component {
            showData = ()=>{
                const {input1,innput2} = this.refs
                console.log(input1);
            }
            render() {
                return (
                    <div>
                        <input ref="input1" type="text"/>
                        <br/>
                        <br/>
                        <button onClick={this.showData}>点击确认文字</button>
                        <input ref="input2" type="text"/>
                    </div>
                )
            }
        }

        ReactDOM.render(<Demo/>,document.querySelector('#app'))
    </script>
  1. 回调函数形式的ref(便捷 使用最多) 要点:
  • currentNode => this.input1 = currentNode 就是给组件实例添加 input1 属性,值为节点
  • 由于是箭头函数,因此 this 是 render 函数里的 this ,即组件实例
  • 回调ref中调用次数问题:原文(如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。见官方文档
    <script type="text/babel">
        class Demo extends React.Component {
            showData = ()=>{
                const {input1,innput2} = this
                console.log(input1);
            }
            render() {
                return (
                    <div>
                        <input ref={(currentNode) => {this.input1=currentNode}} type="text"/>
                        <br/>
                        <br/>
                        <button onClick={this.showData}>点击确认文字</button>
                        <input ref="input2" type="text"/>
                    </div>
                )
            }
        }

        ReactDOM.render(<Demo/>,document.querySelector('#app'))
    </script>
  1. createRef API(官方推荐使用) 该方式通过调用 React.createRef 返回一个容器(对象)用于存储节点,且一个容器只能存储一个节点。
    <script type="text/babel">
        class Demo extends React.Component {
            myRef = React.createRef()
            showData = ()=>{
                console.log(this.myRef);//{current: input}
                alert(this.myRef.current.value)
            }
            render() {
                return (
                    <div>
                        <input ref={this.myRef} type="text"/>
                        <br/>
                        <br/>
                        <button onClick={this.showData}>点击确认文字</button>
                    </div>
                )
            }
        }

        ReactDOM.render(<Demo/>,document.querySelector('#app'))
    </script>

事件处理

  • React 使用自定义事件,而非原生 DOM 事件,即 onClick、onBlur :为了更好的兼容性
  • React 的事件通过事件委托方式进行处理:为了高效
  • 通过 event.target 可获取触发事件的 DOM 元素:勿过度使用 ref 当触发事件的元素和需要操作的元素为同一个时,可以不使用 ref :
class Demo extends React.Component {
  showData2 = (event) => {
    alert(event.target.value)
  }

  render() {
    return (
      <div>
        <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
        &nbsp;
      </div>
    )
  }
}

受控组件&非受控组件

非受控组件:现用现取。即需要使用时,再获取节点得到数据 受控组件:类似于 Vue 双向绑定的从视图层绑定到数据层(推荐使用,因为非受控组件需要使用大量的 ref 。)

  1. 受控组件
<script type="text/babel">
        class Demo extends React.Component {
            state = {
                username:'1',
                password:'2'
            }
            saveUsername = (event)=>{
                this.setState({username:event.target.value})
            }
            savePassword = (event)=>{
                this.setState({password:event.target.value})
            }
            handleSubmit = (event) => {
                event.preventDefault() //阻止表单默认提交
                const {username,password} = this.state
                alert(`${username},${password}`)
            }
            render() {
                return (
                    <form action="" onSubmit={this.handleSubmit}>
                        用户名:<input onChange = {this.saveUsername} type="text"/>
                        密码:  <input onChange = {this.savePassword} type="password"/>
                        <button>登录</button>
                    </form>
                )
            }
            
        }
        
        ReactDOM.render(<Demo/>,document.querySelector('#app'))
    </script>
  1. 非受控组件
    <script type="text/babel">
        class Demo extends React.Component {
            handleSubmit = (event) => {
                event.preventDefault() //阻止表单默认提交
                const {username,password} = this
                alert(`${username.value},${password.value}`)
            }
            render() {
                return (
                    <form action="" onSubmit={this.handleSubmit}>
                        用户名:<input ref={c => this.username = c} type="text"/>
                        密码:  <input ref={c => this.password = c} type="password"/>
                        <button>登录</button>
                    </form>
                )
            }
            
        }
        
        ReactDOM.render(<Demo/>,document.querySelector('#app'))
    </script>

补充:高阶函数&函数柯里化

高阶函数:参数为函数或者返回一个函数的函数,常见的如 Promise、setTimeout、Array.map()等 函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式 使用高阶函数简化受控组件:

    <script type="text/babel">
        class Demo extends React.Component {
            // 初始化state
            state = {
                username:'1',
                password:'2'
            }

            // 自定义事件
            saveFormData = (dateType)=>{
                return (event) => {
                    this.setState({[dateType]:event.target.value})
                }
            }

            handleSubmit = (event) => {
                event.preventDefault() //阻止表单默认提交
                const {username,password} = this.state
                alert(`${username},${password}`)
            }

            render() {
                return (
                    <form action="" onSubmit={this.handleSubmit}>
                        用户名:<input onChange = {this.saveFormData('username')} type="text"/>
                        密码:  <input onChange = {this.saveFormData('password')} type="password"/>
                        <button>登录</button>
                    </form>
                )
            }
            
        }

        // 渲染组件
        ReactDOM.render(<Demo/>,document.querySelector('#app'))
    </script>

组件的生命周期

理解

  • 组件从创建到死亡它会经历一些特定的阶段。
  • React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
  • 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

生命周期(旧)

旧版生命周期 1.3.1 初始化阶段

  • 由ReactDOM.render()触发—初次渲染
  • constructor() —— 类组件中的构造函数
  • componentWillMount() —— 组件将要挂载 【即将废弃】
  • render() —— 挂载组件
  • componentDidMount() —— 组件挂载完成 比较常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

1.3.2 更新阶段 【第一种情况】父组件重新render触发

  • componentWillReceiveProps() —— 接收属性参数(非首次)【即将废弃】
  • ... 【第二种情况】由组件内部this.setSate()
  • shouldComponentUpdate() —— 组件是否应该被更新(默认返回true)
  • ... 【第三种情况】强制更新 forceUpdate()
  • componentWillUpdate() ——组件将要更新 【即将废弃】
  • render() —— 组件更新
  • componentDidUpdate() —— 组件完成更新

1.3.3 卸载组件

  • 由ReactDOM.unmountComponentAtNode()触发
  • componentWillUnmount() —— 组件即将卸载

生命周期(新)

生命周期 16+

  1. 初始化阶段
  • 由ReactDOM.render()触发 —— 初次渲染
  • constructor() —— 类组件中的构造函数
  • static getDerivedStateFromProps(props, state) 从props得到一个派生的状态【新增】
  • render() —— 挂载组件
  • componentDidMount() —— 组件挂载完成 比较常用
  1. 更新阶段
  • 由组件内部this.setSate()或父组件重新render触发或强制更新forceUpdate()
  • getDerivedStateFromProps() —— 从props得到一个派生的状态 【新增】
  • shouldComponentUpdate() —— 组件是否应该被更新(默认返回true)
  • render() —— 挂载组件
  • getSnapshotBeforeUpdate() —— 在更新之前获取快照【新增】
  • componentDidUpdate(prevProps, prevState, snapshotValue) —— 组件完成更新
  1. 卸载组件
  • 由ReactDOM.unmountComponentAtNode()触发
  • componentWillUnmount() —— 组件即将卸载
    <script type="text/babel">
        class Demo extends React.Component {
            state = {newsArr:[]}

            componentDidMount() {
                setInterval(() => {
                    // 获取状态
                    let {newsArr} = this.state
                    const news = '列表'+(newsArr.length+1)
                    // 更新状态
                    this.setState({newsArr:[news,...newsArr]})
                }, 1000);
            }
            getSnapshotBeforeUpdate() {
                return this.refs.list.scrollHeight
            }
            componentDidUpdate(preProps,preState,height) {
                this.refs.list.scrollTop += this.refs.list.scrollHeight - height
            }
            render() {
                return (
                    <div>
                        <ul className="list" ref="list">
                            {
                                this.state.newsArr.map((n,index) => {
                                    return <li key={index} className="news">{n}</li>
                                })
                            } 
                        </ul>
                    </div>
                )
            }
        }
        ReactDOM.render(<Demo/>,document.querySelector("#app"))
        
    </script>

diffing算法与key

  1. 虚拟DOM中key的作用
  • 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
  • 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
	(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
	(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
	根据数据创建新的真实DOM,随后渲染到到页面

!https://img-blog.csdnimg.cn/2021060821262976.png 2. 用index作为key可能会引发的问题

  • 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
  • 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题

注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的

第三章 React脚手架

脚手架安装与使用

全局安装配置 :npm i -g create-react-app 创建项目:create-react-app 项目名

C:\Users\Mrnianj>npm i -g create-react-app
...
C:\Users\Mrnianj>create-reacrt-app 项目名
...
C:\Users\Mrnianj>cd 项目(文件夹)名
...
C:\Users\Mrnianj>npm start

脚手架目录:

public ---- 静态资源文件夹
	favicon.icon ------ 网站页签图标
	index.html -------- 主页面
	logo192.png ------- logo图
	logo512.png ------- logo图
	manifest.json ----- 应用加壳的配置文件
	robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
	App.css -------- App组件的样式
	App.js --------- App组件
	App.test.js ---- 用于给App做测试
	index.css ------ 样式
	index.js ------- 入口文件
	logo.svg ------- logo图
	reportWebVitals.js
		--- 页面性能分析文件(需要web-vitals库的支持)
	setupTests.js
		---- 组件单元测试的文件(需要jest-dom库的支持)

最简单的项目实例

public/index.html

<body>
  <div id="root"></div>
</body>

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

src/app.js

// 创建外壳组件
import  React,{Component}  from "react";
import Hello from "./components/Hello/Hello"
import Welcome from "./components/Welcome"
// 暴露并创建组件
export default class App extends Component {
    render() {
        return (
            <div>
               <Hello/> 
               <Welcome/> 
            </div>
        )
    }
}

src/components/Hello

import React ,{ Component } from "react";

export default class Hello extends  Component{
    render() {
        return (
            <h1>Hello React!!!</h1>
        )
    }
}

src/components/Welcome

import React,{Component} from "react"

export default class Welcome extends Component {
    render() {
        return <h1>Welcome</h1>
    }
}

TodoList案例

  • 拆分组件、实现静态组件,注意:className 、style 的写法
  • 动态初始化列表,如何确定将数据放在哪个组件的 state 中? 某个组件使用:放在其自身的 state 中 某些组件使用:放在他们共同的父组件 state 中,即状态提升
  • 关于父子之间通信: 父传子:直接通过 props 传递 子传父:父组件通过 props 给子组件传递一个函数,子组件调用该函数
// 父组件
class Father extends Component {
  state: {
    todos: [{ id: '001', name: '吃饭', done: true }],
    flag: true,
  }

  addTodo = (todo) => {
    const { todos } = this.state
    const newTodos = [todo, ...todos]
    this.setState({ todos: newTodos })
  }

  render() {
    return <List todos={this.state.todos} addTodo={this.addTodo} />
  }
}

// 子组件
class Son extends Component {
  // 由于 addTodo 是箭头函数,this 指向父组件实例对象,因此子组件调用它相当于父组件实例在调用
  handleClick = () => {
    this.props.addTodo({ id: '002', name: '敲代码', done: false })
  }

  render() {
    return <button onClick={this.handleClick}>添加</button>
  }
}
  • 注意(标签属性) defaultChecked 和 checked 的区别,类似的还有:defaultValue 和 value
  • 状态在哪里,操作状态的方法就在哪里

第四章 React网络请求(ajax)

axios | 网络请求

安装 axios npm i axios

export default class App extends Component {
  getStuData = ()=> {
    axios.get('http://localhost:3000/api1/students').then(
      response => {console.log(response.data);},
      err => {console.log(err);}
    )
  }

  getCarData = ()=> {
    axios.get('http://localhost:3000/api2/cars').then(
      response => {console.log(response.data);},
      err => {console.log(err);}
    )
  }

  render() {
    return (
      <div>
        <button onClick={this.getStuData}>获取学生</button>
        <button onClick={this.getCarData}>获取汽车</button>
      </div>
    )
  }
}

React 脚手架配置代理(跨域问题)

官方文档 方法1: 优点:配置简单,前端请求资源可不加前缀 缺点:不能配置多个代理 工作方式:当请求了 3000 端口号(本机)不存在的资源时,就会把请求转发给 5000 端口号服务器 在 package.json 文件中进行配置:"proxy": "http://localhost:5000" 方法2: 在 src 目录下创建代理配置文件 setupProxy.js ,进行配置:

// const proxy = require('http-proxy-middleware')
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
  app.use(
    //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
    createProxyMiddleware('/api1', {
      //配置转发目标地址(能返回数据的服务器地址)
      target: 'http://localhost:5000',
      //控制服务器接收到的请求头中host字段的值
      /*
      changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
      changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
      changeOrigin默认值为false,但一般将changeOrigin改为true
      */
      changeOrigin: true,

      //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
      pathRewrite: { '^/api1': '' },
    }),
    createProxyMiddleware('/api2', {
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: { '^/api2': '' },
    })
  )
}

消息订阅与发布机制

React 中兄弟组件或任意组件之间的通信方式。 使用的工具库:pubsub-js

  1. 安装 npm install pubsub-js --save
  2. 基础用法示例
// A组件
import PubSub from 'pubsub-js'
// 组件被挂载后订阅消息
componentDidMount() {
  this.token = PubSub.subscribe('topic', (msg,data)=>{//msg 订阅消息名,data 传递的数据
    console.log('List组件收到订阅消息',data);
    this.setState(data)
  })
}
// 组件将被卸载时取消订阅
componentWillUnmount() {
  PubSub.unsubscribe(this.token)
}

// B组件
// 发布消息
import PubSub from 'pubsub-js'
export default class Search extends Component {

  saveUsers = ()=>{
    // 发布消息

    // 发送网络请求后 通知A组件更新状态
    PubSub.publish('topic', {isLoading:true,isFirst:false})
    axios.get(`https://api.github.com/search/users?q=username`).then(
      (res)=>{ 
        PubSub.publish('topic', {isLoading:false,users:res.data.items})
      },
      (err)=>{
        PubSub.publish('topic', {isLoading:false,err})
      }
    )
  }
}

扩展 fetch | 发送ajax请求(了解即可)

阮一峰 Fetch API 教程 常见可以发送ajax请求的方式

  • xhr (常见的有:jQuery\axios)
  • fetch (原生,但是不常用) 下面是fetch使用
// 使用fetch发送请求
fetch(url)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(e => console.log("获取数据失败", e))

// 使用 await 语法优化
async function getJSON() {
  let url = 'https://api.github.com/users/ruanyf';
  try {
    let response = await fetch(url);
    return await response.json();
  } catch (error) {
    console.log('请求出错', error);
  }
}

Github 搜索框案例知识点总结

  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
  2. ES6 知识点:解构赋值 + 重命名
let obj = { a: { b: 1 } }

//传统解构赋值
const { a } = obj

//连续解构赋值
const {
  a: { b },
} = obj

//连续解构赋值 + 重命名
const {
  a: { b: value },
} = obj
  1. 消息订阅与发布机制
  • 先订阅,再发布(隔空对话)
  • 适用于任意组件间通信
  • 要在 componentWillUnmount 钩子中取消订阅
  1. fetch 发送请求(关注分离的设计思想)
try {
  // 先看服务器是否联系得上
  const response = await fetch(`/api1/search/users2?q=${keyWord}`)
  // 再获取数据
  const data = await response.json()
  console.log(data)
} catch (error) {
  console.log('请求出错', error)
}

第五章 React路由

SPA页面&路由理解

  1. SPA页面的特点
  • 单页web应用
  • 整个应用只有一个完整页面
  • 点击页面链接不会刷新页面,只会做页面的局部刷新
  • 数据都需要经过ajax请求获取,并在前端一部展现
  1. 何为路由?
  • 一个路由是一个映射关系
  • key 为路径地址,value 可能是 function 或 component
  1. 路由分类
  • 后端路由:
    • value 是 function ,用于处理客户端的请求
    • 注册路由:router.get(path, function(req, res))
    • 工作过程:Node 接收到请求,根据路径匹配路由,调用对应函数处理请求,返回响应数据
  • 前端路由:
    • value 是组件
    • 注册路由:<Route path="/test" component={Test}>
    • 工作过程:浏览器路径变为 /test ,展示 Test 组件

补充:路由的基本原理

window.history BOM 浏览器对象中包含 history 对象用于管理浏览器历史记录,History 对象是 window 对象的一部分,可通过 window.history 属性对其进行访问。

方法	        说明
back()	        加载 history 列表中的前一个 URL
forward()	    加载 history 列表中的下一个 URL
go()	        加载 history 列表中的某个具体页面

react-router-dom | 路由插件

react的一个插件库,用来实现一个SPA应用。 react-router web应用官方文档

路由的基本使用

  1. 安装 react-router-dom :
// 安装 5.X 版本路由
npm install react-router-dom@5.2.0 -S
是  
// 最新已经 6.X 版本,用法和 5.X 有所不同
npm install react-router-dom -S

6.x 版本的基本使用参考 2. 编写基本路由 index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter} from "react-router-dom";

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

App.jsx

import React, { Component } from 'react'
import About from './components/About';
import Home from './components/Home';
import { Link,Route} from "react-router-dom";

export default class App extends Component {
  render() {
    return (
      <div>
        <div>
          {/* 标题 */}
          <div className="row">
            <div className="col-xs-offset-2 col-xs-8">
              <div className="page-header"><h2>React Router Demo</h2></div>
            </div>
          </div>
          {/* 导航栏 */}
          <div className="row">
            <div className="col-xs-2 col-xs-offset-2">
              <div className="list-group">
                {/*编写路由链接*/}
                <Link className="list-group-item" to="/about">About</Link>
                <Link className="list-group-item" to="/Home">Home</Link>
              </div>
            </div>
            {/* 内容区    */}
            <div className="col-xs-6">
              <div className="panel">
                <div className="panel-body">
                  {/*注册路由*/}
                  <Route path='/home' component={Home}/>
                  <Route path='/about' component={About}/>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}
  1. 总结
  • 导航区正宗的跳转链接改为Link标签
  • 展示区写的Route标签进行路由匹配
  • index.js入口文件中的<App>外侧需要包裹一个<BrowserRouter>或者<HashRouter>标签

路由组件和一般组件

  1. 存放位置不同: 一般组件:components 路由组件:pages
  2. 写法不同: 一般组件: 路由组件:
  3. 接收到的 props 不同: 一般组件:标签属性传递 路由组件:会接收到三个固定的属性(history,location=history.location | 语法糖,match
history:
  go: ƒ go(n)
  goBack: ƒ goBack()
  goForward: ƒ goForward()
  push: ƒ push(path, state)
  replace: ƒ replace(path, state)

location:
  pathname: "/home/message/detail/2/hello"
  search: ""
  state: undefined

match:
  params: {}
  path: "/home/message/detail/:id/:title"
  url: "/home/message/detail/2/hello"

NavLink的使用

NavLink是Link的迭代,NavLink 可以通过 activeClassName 属性指定点击之后追加的样式名,默认追加类名 active ,

<NavLink activeClassName="demo" to="/about">About</NavLink>
<NavLink activeClassName="demo" to="/home">Home</NavLink>

封装NavLink: MyNavLink.jsx

/*
    针对 NavLink 的二次封装
*/
import React, { Component } from 'react'
import { NavLink } from "react-router-dom";

export default class MyNavLink extends Component {
  render() {
      let {} = this.props
    return (
        // <NavLink className="list-group-item" {...this.props}>{this.props.children}</NavLink>
        <NavLink className="list-group-item" {...this.props} />
    )
  }
}

调用方式 <MyNavLink to="/11">MyNavLink</MyNavLink>

Switch 单一路由的匹配

  1. 一般情况下,一个路由对应一个组件(需要借助Switch),注意:在v6.xx版本中,Switch 已经被 Routes 标签代替!
  2. Switch 可以提高路由匹配效率,如果匹配成功,则不再继续匹配后面的路由,即单一匹配。
import { Route,NavLink,Switch } from "react-router-dom";
{/* ... */}

{/* 编写路由链接 */}
<NavLink to="/about">About</NavLink>
<NavLink to="/home">Home</NavLink>

{/* 注册路由 包裹 Switch 标签 */}
<Switch>
  <Route path='/home' component={Home}/>
  <Route path='/about' component={About}/>
</Switch>

解决多级路由样式丢失

当路由路径中出现多级路由时,如<Route path='/home/index' component={Home} />时,页面加载后可能会出现样式文件丢失 三种解决方案:

  1. public/index.html 中 引入样式时不写 ./ 写 / (常用),如:<link rel="stylesheet" href="/bootstrap.css">
  2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用),如:<link rel="stylesheet" href="%PUBLIC_URL%/bootstrap.css">
  3. 使用 HashRouter(哈希路由/锚点路由),如:
import { HashRouter } from "react-router-dom";

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

路由的严格匹配与模糊匹配

模糊匹配(左侧匹配)示例

<NavLink to="/home/a">Home</NavLink>

<Switch>
  {/* 可以匹配 */}
  <Route path='/home' component={Home}/>
</Switch>

开启严格匹配示例

<NavLink to="/about">Home</NavLink>

<Switch>
  {/* 可以匹配 */}
  <Route exact path="/about" component={About}/>
  {/* 不可以匹配 */}
  <Route exact path="/about/a" component={About}/>
</Switch>
  • react-router-dom 默认使用模糊匹配(输入的路径必须包含要匹配的路径,且是顺序匹配)
  • 使用exact={true}(语法糖:可以省略属性值) 开启严格匹配:<Route exact path="/about" component={About}/>
  • 严格匹配需要再开,开启可能会导致无法继续匹配二级路由

Redirect | 重定向、重定向路由

一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由

import { Route,Switch,Redirect } from "react-router-dom";

<Switch>
  <Route path="/about" component="{About}" />
  <Route path="/home" component="{Home}" />
  <Redirect to="/about" />
</Switch>

多级路由

注册子路由需写上父路由的 path 路由的匹配是按照注册路由的顺序进行的 home.jsx | 不要开启严格匹配

<!-- 父组件 -->
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>

<Switch>
  <Route path="/about" component="{About}" />
  <Route path="/home" component="{Home}" />
  <Redirect to="/about" />
</Switch>

/pages/home/news.jsx & ../message.jsx

<!-- 子组件 -->
<ul className="nav nav-tabs">
  <li>
    <MyNavLink to="/home/news">News</MyNavLink>
  </li>
  <li>
    <MyNavLink to="/home/message">Message</MyNavLink>
  </li>
</ul>

<Switch>
  <Route path="/home/news" component="{News}" />
  <Route path="/home/message" component="{Message}" />
  <Redirect to="/home/news" />
</Switch>

路由传参

三种方式:params, search, state 参数 state 方式当前页面刷新可保留参数,但在新页面打开不能保留。 params 和 search 参数都会变成字符串,两种方式由于参数保存在 URL 地址上,因此都能保留参数。

  1. 传递 params 参数方式
{/* 向路由组件传递 params 参数 */}
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
{/* 声明接收 params 参数 */}
<Route path='/home/message/detail/:id/:title' component={Detail}/>

// 接收 params 参数
const {id,title} = this.props.match.params
  1. 传递 search 参数方式(需要自己处理传递后的参数,较麻烦)
{/* 向路由组件传递 search 参数 */}
<Link to={`/home/message/detail/?id:${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
{/* search 参数无需声明接收 */}
<Route path='/home/message/detail' component={Detail}/>

// 接收 params 参数(需要自己处理 字符串截取转对象)
const {search } = this.props.location  //search:"?id=01&title=消息1"
// 处理 search 参数
let searchObj = {}
var splitArr = search.slice(1,search.length).split("&") //['id=03','title=消息3']
splitArr.map((item) => {
  let Objkey,Objvalue = ''
  let Obj = {}
  item.split("=").map((value,index) => {
    index==0 ? Objkey = value : Objvalue = value
  })
  Obj[Objkey] = Objvalue
  searchObj = {...searchObj,...Obj}
})
  1. 传递 state 参数方式
{/* 向路由组件传递 state 参数 */}
<Link to={{pathname:'/home/message/detail',state:{ id:msgObj.id,title:msgObj.title }}}>{msgObj.title}</Link>
{/* state 参数也无需声明接收 */}
<Route path='/home/message/detail' component={Detail}/>

// 接收 state 参数
const { id,title } = this.props.location.state || {}

补充:开启replace模式 ,rreact-router-dom默认启用push模式 push与replace的区别 push 压栈操作,会产生历史记录 replace 替换操作,不会产生历史记录,无法返回

<Link replace={true} to='/home/message/detail'>{msgObj.title}</Link>
// 简写( == 严格匹配模式)
<Link replace to='/home/message/detail'>{msgObj.title}</Link>

编程式路由

通过 history 对象提供的api实现路由跳转 history: props.history.go: ƒ go(n) 参数传递整数(正数前进 负数后退) props.history.goBack: ƒ goBack() 前进 props.history.goForward: ƒ goForward() 后退 props.history.push: ƒ push(path, state) 添加历史记录 props.history.replace: ƒ replace(path, state) 替换历史记录

// 三种编程式导航传参的方式
this.props.history.push(`/home/message/detail/${id}/${title}`)
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
this.props.history.push(`/home/message/detail`, { id: id, title: title })

withRouter 的使用

withRouter 的作用:加工一般组件,让其拥有路由组件的 API ,如 this.props.history.push 等。

import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'

class Header extends Component {
  render() {
    {/*...*/}
  }
}

export default withRouter(Header)

BrowserRouter 和 HashRouter

  1. 底层原理不一样: BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本。 HashRouter 使用的是 URL 的哈希值。
  2. 路径表现形式不一样 BrowserRouter 的路径中没有 # ,如:localhost:3000/demo/test HashRouter 的路径包含#,如:localhost:3000/#/demo/test
  3. 刷新后对路由 state 参数的影响 BrowserRouter 没有影响,因为 state 保存在 history 对象中。 HashRouter 刷新后会导致路由 state 参数的丢失!
  4. 备注:HashRouter 可以用于解决一些路径错误相关的问题(多级路由刷新样式丢失问题)。

第六章 UI组件库 antd-UI

Ant Design官方网站

  1. 安装 npm install antd --save
  2. 基本使用
import React, { Component } from 'react'
// 引入antd
import { Button } from 'antd';
import { PoweroffOutlined }from '@ant-design/icons';
import "antd/dist/antd.css";

export default class App extends Component {
  render() {
    return (
      <div>
        <Button type="primary" icon={<PoweroffOutlined />}>
          Click me!
        </Button>
      </div>
    )
  }
}
  1. 3.x按需引入 Antd,默认情况下:组件的js是支持按需引入,但css并不支持按需引入,即默认情况下Antd的CSS样式被整体引入,
  • 安装依赖 npm install react-app-rewired customize-cra babel-plugin-import
  • 修改 package.json
/* package.json */
"scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build",
-   "test": "react-scripts test",
+   "test": "react-app-rewired test",
}
  • 项目根目录下创建 config-overrides.js
//配置具体的修改规则
const { override, fixBabelImports, addLessLoader } = require('customize-cra')

module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  })
)
  1. 补充 :3.x版本配置主题已不适用与antd4.x版本,配置按需引入&定制主题参考: 官网文档 Antd4.x 按需引用&自定义主题-简书

第七章 Redux

阮一峰 Redux入门 Redux 中文文档

Redux 概述

  • Redux 是用于做 状态管理 的 JS 库,除此之外还有DvaJS(推荐)等
  • 可用于 React、Angular、Vue 等项目中,常用于 React
  • 集中式管理 React 应用多个组件共享的状态

何时用 Redux ?

  • 某个组件的状态,需要让其他组件拿到(状态共享)
  • 一个组件需要改变另一个组件的状态(通信)
  • 使用原则:不到万不得已不要轻易动用 Redux 工作流程 Redux工作流程 组件想操作 Redux 中的状态:需要把动作类型和数据(对象形式)告诉 Action Creators
  • Action Creators 创建 action :同步 action 是一个普通对象,异步 action 是一个函数
  • Store 调用 dispatch() 分发 action 给 Reducers 执行
  • Reducers 接收 previousState 、action 两个参数,对状态进行加工后返回新状态
  • Store 调用 getState() 把状态传给组件

redux基本使用:

store.js

// 引入 createStore 创建 store 对象
import { createStore } from "redux";
// 引入为 Count 服务的 countReducer
import countReducer from "./reduce";

// 暴露 store
export default createStore(countReducer)

reduce.js

/*
    为Count组件创建的服务reducer
    作用:
        初始化store
        接收两个参数:之前的状态(preState),动作对象(action)
*/
const initState = 0
export default function countReducer(preState = initState,action) {//默认参数
    const { type,data } = action
    switch (type) {
        case 'increment':
          return data + preState
        default:
          return preState//无参数,返回默认值 0 
    }
}

index.jsx

import React, { Component } from 'react'
import store from "../../redux/store";

export default class Count extends Component {
  componentDidMount(){
    // 组件挂载完毕,监测状态更新,重新渲染
    store.subscribe(()=>{
        this.setState({})
    })
  }
  // 加
  increment = ()=>{
    // 获取用户输入数据
    const userInput = (this.selectNumber.value)*1
    // 通知redux 更新状态
    store.dispatch({type:'increment',data:userInput})
  }    
  render() {
    return (
      <div className='Count'>
          <p>
            当前求和结果为:Redux:{store.getState()}
          </p>
          <br/>
          <select ref={c => this.selectNumber = c}>
              <option>1</option>
              <option>2</option>
          </select>
          <button onClick = {this.increment}>+</button>

      </div>
    )
  }
}

补充:监测redux中数据改变重新绘排,可以在index.js入口文件中监测整个App

// ...
store.subscribe(()=>{
  ReactDom.render(<App/>,document.querySelector('#root'))
})

redux完整流程

  1. 文件结构 redux
  • constant.js 定义 action 中 type 的常量值,一次定义,多处引用
  • count_action.js 创建 action 对象
  • count_reducer.js 1.初始化store 2.接收两个参数:之前的状态(preState),动作对象(action)
  • store.js
// redux/count_action.js

import { INCREMENT, DECREMENT } from './constant'

export const createIncrementAction = (data) => ({ type: INCREMENT, data })
export const createDecrementAction = (data) => ({ type: DECREMENT, data })
// redux/constant.js
// 保存常量值
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

Redux 异步Action

要点:

  • 延迟的动作不想交给组件,而是 action
  • 当操作状态所需数据要靠异步任务返回时,可用异步 action
  • 创建 action 的函数返回一个函数,该函数中写异步任务
  • 异步任务完成后,分发一个同步 action 操作状态
  • 异步 action 不是必要的,完全可以在组件中等待异步任务结果返回在分发同步 action
  1. 安装 异步支持中间件 npm install redux-thunk -S
  2. 定义异步 action
// count_action.js
import { INCREMENT, DECREMENT } from './constant.js'

// 同步 action 返回一个对象
export const createIncrementAction = (data) => ({ type: INCREMENT, data })
export const createDecrementAction = (data) => ({ type: DECREMENT, data })

// 异步 action 返回一个函数
export const createIncrementAsyncAction = (data, time) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(createIncrementAction(data))
    }, time)
  }
}
  1. 配置 thunk 插件
// store.js
// 引入 applyMiddleware 异步支持函数
import { createStore, applyMiddleware } from 'redux'
import countReducer from './count_reducer'
import thunk from 'redux-thunk'

export default createStore(countReducer, applyMiddleware(thunk))
  1. 正常调用异步 action
// Count.jsx
incrementAsync = () => {
  const { value } = this.selectNumber
  store.dispatch(createIncrementAsyncAction(value * 1))
}

<button onClick = {this.incrementAsync}>异步加</button>

总结: store 在分发 action 时,发现返回一个函数(为异步 action)。因此 store 执行这个函数,同时给这个函数默认传递 dispatch 参数,等待异步任务完成取到数据后,直接调用 dispatch 方法分发同步 action 。

react-redux

Redux 官方提供的 React 绑定库。 具有高效且灵活的特性。 react-redux 中文文档 react-redux模型图

react-redux的基本使用

安装: npm install react-redux store.js

// 引入 createStore 创建 store 对象
import { createStore } from "redux";
// 引入为 Count 服务的 countReducer
import countReducer from "./count_reduce";

// 暴露 store
export default createStore(countReducer)

count_action.js

import { INCREMENT,DECREMENT } from "./constant";

export const createIncrementAction = data => ( {type:INCREMENT,data:data} )
export const createDecrementAction = data => ( {type:DECREMENT,data:data} )

count_reducer.js

/*
    为Count组件创建的服务reducer
    作用:
        初始化store
        接收两个参数:之前的状态(preState),动作对象(action)
*/
import { INCREMENT,DECREMENT } from "./constant";
const initState = 0 //初始化默认参数
export default function countReducer(preState = initState,action) {//默认参数
    const { type,data } = action
    switch (type) {
        case INCREMENT:
          return data + preState
        case DECREMENT:
          return preState - data 
        default:
          return preState//无参数,返回默认值 0 
    }
}

App.jsx

/*
  为容器组件传入store 并渲染
*/
import React, { Component } from 'react'
import store from "./redux/store";
import Count from "./containers/Count";
export default class App extends Component {
  render() {
    return (
      <div>
          <h3>求和案例-react实现</h3>
          <hr/>
          <Count store={store}/>
      </div>
    )
  }
}

Count.jsx

/*
    CountUI容器组件
    引入connet 链接ui组件与redux
    映射状态和状态方法
*/
import CountUI from "../../components/Count";
import { connect } from "react-redux";
import { createIncrementAction,createDecrementAction,createIncrementActionAsync } from "../../redux/count_action";

// 映射状态
function mapStateToProps(state) {
    return {count:state}
}
// 映射操作状态的方法
function mapDispathToProps(dispath) {
    return {
        increment: inputNumber => {dispath(createIncrementAction(inputNumber))},
        decrement: inputNumber => {dispath(createDecrementAction(inputNumber))},
        incrementAsync: (inputNumber,time) => {dispath(createIncrementActionAsync(inputNumber,time))},
    }
}

// 映射状态和方法  并暴露容器组件
export default connect(
    mapStateToProps,
    mapDispathToProps
)(CountUI)

CountUI.jsx

import React, { Component } from 'react'
import './Count.css'

export default class Count extends Component {
  
  // 加
  increment = ()=>{
    const userInput = (this.selectNumber.value)*1
    this.props.increment(userInput)
  }
  //   减
  decrement = ()=>{
    const userInput = (this.selectNumber.value)*1
    this.props.decrement(userInput)
  }
  //   奇数加
  incrementIfOdd = ()=>{
    const userInput = (this.selectNumber.value)*1
    if (this.props.count % 2 !== 0) {
      this.props.increment(userInput)
    }
  }
  //   异步加
  incrementAsync = ()=>{
    const userInput = (this.selectNumber.value)*1
    this.props.incrementAsync(userInput,500)
  }
  render() {
    // const userInput = (this.selectNumber.value)*1
    const {count} = this.props
    return (
      <div className='Count'>
          <p>
            当前求和结果为:{count}
          </p>
          <br/>
          <select ref={c => this.selectNumber = c}>
              <option>1</option>
              <option>2</option>
              <option>3</option>
          </select>
          <button onClick = {this.increment}>+</button>
          <button onClick = {this.decrement}>-</button>
          <button onClick = {this.incrementIfOdd}>求和结果为奇数加</button>
          <button onClick = {this.incrementAsync}>异步加</button>
      </div>
    )
  }
}

react-redux的优化

  1. mapDispatchToProps 可以写成对象形式,React-Redux 底层会帮助自动分发。
...

export default connect(
    state => ({count:state}),// 映射状态 
    {
        increment:createIncrementAction,
        decrement:createDecrementAction,
        incrementAsync:createIncrementActionAsync
    } //映射状态的简写方式(对象 react-redux 会自动 分发dispath)
)(Count)
  1. React-Redux 容器组件可以自动监测 Redux 状态变化,因此 index.js 不需要手动监听:
  2. index.js入口文件中 Provider 组件可以让所有组件都能获得状态数据,不必一个一个传递
  3. 整合容器组件和 UI 组件为一个文件:
import React, { Component } from 'react'
import {
    createIncrementAction,
    createDecrementAction,
} from '../../redux/count_action'
import {connect} from 'react-redux'

// 定义 UI 组件
class Count extends Component {
  ...
}

// 创建容器组件
export default connect(
  state => ({count: state}),
  {
    add: createIncrementAction,
    sub: createDecrementAction
  }
)(Count)

多组件共享状态

文件结构, 容器组件和 UI 组件合为一体后放在 containers 文件夹(存放react-redux管理的需要管理状态的组件)。 redux 文件夹新建 actions 和 reducers 文件夹分别用于存放每个容器组件需要的 action 和 reducer 。 重点:在 store.js 中引入 combineReducers() 整合多个 reducer 来合并总状态对象,组件中通过对象的形式访问 redux/store.js | redux中管理状态的store模块

// 引入 createStore 创建 store 对象
import { createStore,applyMiddleware,combineReducers } from "redux";
// 引入 redux-thunk , 用于支持异步action
import thunk from 'redux-thunk'
// 引入为 Count 服务的 countReducer
import countReducer from "./reducers/count"
// 引入为 Persion 服务的 countReducer
import persionReducer from "./reducers/persion"

// 汇总 reducers
const allReducers = combineReducers({
    sum: countReducer,//合并后的key 会作为总状态对象中的key,value为状态
    persions: persionReducer
})
// 暴露 store
export default createStore(allReducers,applyMiddleware(thunk))

redux/actions/persion.js | 创建action对象模块

import { ADDPERSION } from "../constant";
export const createAddPersionAction = data => ({ type:ADDPERSION,data })

redux/reducers/persion.js | 初始化状态和处理状态的reducer模块

import { ADDPERSION } from "../constant";

const initState = [{id:136780,name:'root',age:'0'}]
export default function persionReducer(preState=initState,action) {
    const { type,data } = action
    switch (type) {
        case ADDPERSION:
            return [data,...preState]
        default:
            return preState
    }
}

redux/constant.js | redux中的常量模块

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADDPERSION = 'addPersion'

containers/Persion/index.jsx | UI组件和容器组件

import React, { Component } from 'react'
import { connect } from "react-redux";
import { nanoid } from "nanoid";
import { createAddPersionAction } from "../../redux/actions/persion";
import './Persion.css'

class demoUI extends Component {
  addPersion = ()=>{
    let name = this.nameNode.value
    let age = this.ageNode.value
    const persionObj = {id:nanoid(),name,age}
    this.props.addPersionObj(persionObj)
  }
  render() {
    return (
      <div>
        <h2>Perssion组件,上方组件总和为{this.props.sum}</h2>
        <input ref = {c => this.nameNode = c} type="text" id="name" placeholder='请输入你的名字' />
        <input ref = {c => this.ageNode = c} type="text" id="age"  placeholder='请输入你的年龄'/>
        <button onClick={this.addPersion}>添加</button>
        <ul>
          {
            this.props.persionObjArr.map((persionObj)=>{
              return <li key={persionObj.id}> 姓名:{persionObj.name} ,年龄:{persionObj.age} </li>
            })
          }
        </ul>
      </div>
    )
  }
}
export default connect(
    state => ({ 
      persionObjArr:state.persions ,
      sum:state.sum ,
    }),
    { addPersionObj: createAddPersionAction }
)(demoUI)

补充 数组与对象方法

let arr = [1,2]
// 给arr数组追加并return,下面两种方式的区别
return ['a','b',...arr]
arr.unshift('a','b')
return arr
/*
  返回的内存地址不同
*/

Redux 开发者工具

  1. Chrome 安装 Redux DevTools 扩展工具
  2. 项目下载依赖包 npm i redux-devtools-extension --save-dev
  3. 配置:
redux/store.js
import { composeWithDevTools } from 'redux-devtools-extension'
...
//需要异步中间件
export default createStore(Reducers, composeWithDevTools(applyMiddleware(thunk)))
// 不需要异步中间件
export default createStore(Reducers, composeWithDevTools())

foot

空文件

简介

React学习笔记备份 视频链接:https://www.bilibili.com/video/BV1wy4y1D7JT 展开 收起
取消

发行版

暂无发行版

贡献者 (1)

全部

近期动态

接近3年前推送了新的提交到 master 分支,90830cb...15bd5d5
3年前推送了新的 master 分支
3年前创建了仓库
不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/mr-nianj/react-learning-record.git
git@gitee.com:mr-nianj/react-learning-record.git
mr-nianj
react-learning-record
张天禹React基础学习记录
master

搜索帮助