React入门|只要卷不死,就往死里卷~

374 阅读35分钟

image.png

还在更新中~

最近面试发现react工作需求比较多,为了高薪offer,加油卷起来~

React官网

1. 英文官网: https://reactjs.org/

2. 中文官网: react.docschina.org/

React是什么?

React是一个将数据渲染为HTML视图的开源JavaScriot库

学习React的优点?

  1. 原生JavaScript操作DOM繁琐、效率低
  2. 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
  3. 原生JavaScript没有组件化编码方案,代码复用率低
  4. React采用组件化模式,声明式编码,提高开发效率及组件复用率
  5. 在React Native中可以使用React语法进行移动端开发
  6. 使用虚拟DOM(会对之前的虚拟DOM进行比较)+diff算法,减少用户造作真实DOM
模块化?组件化?

模块化:将js代码根据功能结构将代码拆分,方便维护 组件化:根据ui结构的拆分(包括结构,样式,功能),方便复用

相关js库

1. react.js:React核心库。

2. react-dom.js:提供操作DOM的react扩展库。

3. babel.min.js:解析JSX语法代码转为JS代码的库(将ES6降级为ES5)。

React的基本使用

<body>
  <!-- 准备好一个容器 -->
  <div id="test"></div>
  <!-- 引入React核心库 全局有React-->
  <script src="../js/react.development.js"></script>
  <!-- 引入React扩展库 全局有React DOM-->
  <script src="../js/react-dom.development.js"></script>
  <!-- 引入babel -->
  <script src="../js/babel.min.js"></script>
  <!-- 表示写的jsx语句  -->
  <script type="text/babel">
    //创建虚拟DOM
    const VDOM = <h1>Hello,React</h1>//此处不要写引号,因为不是字符串

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

用jsx创建虚拟DOM

<body>
  <!-- 准备好一个容器 -->
  <div id="test"></div>
  <!-- 引入React核心库 全局有React-->
  <script src="../js/react.development.js"></script>
  <!-- 引入React扩展库 全局有React DOM-->
  <script src="../js/react-dom.development.js"></script>
  <!-- 引入babel -->
  <script src="../js/babel.min.js"></script>
  <!-- 表示写的jsx语句  -->
  <script type="text/babel">
    //创建虚拟DOM
    const VDOM = (
      <h1 id="title">
        Hello,React
      </h1>
    )//此处不要写引号,因为不是字符串

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

虚拟DOM与真实DOM的区别

  1. 虚拟DOM本质是object类型的对象
  2. 虚拟dom比较“轻”(身上的属性少),虚拟DOM是react内部再用 用不上真实dom上那么多属性
  3. 虚拟dom最终会被react转换为真实dom渲染在页面上

#渲染虚拟DOM(元素)

1. 语法:  ReactDOM . render ( virtualDOM , containerDOM )

2. 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示

3. 参数说明

1) 参数一: 纯js或jsx创建的虚拟dom对象

2) 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

<body>
  <!-- 准备好一个容器 -->
  <div id="test"></div>
  <!-- 引入React核心库 全局有React-->
  <script src="../js/react.development.js"></script>
  <!-- 引入React扩展库 全局有React DOM-->
  <script src="../js/react-dom.development.js"></script>
  <!-- 引入babel -->
  <script src="../js/babel.min.js"></script>
  <!-- 表示写的jsx语句  -->
  <script type="text/babel">
    //创建虚拟DOM
    const VDOM = (
      <h1 id="title">
        <span>Hello,React</span>
      </h1>
    )//此处不要写引号,因为不是字符串

    // 渲染虚拟DOM到页面
    // ReactDOM.render(虚拟DOM名,容器)
    ReactDOM.render(VDOM, document.getElementById('test'))
    const TDOM = document.getElementById('test')
    console.log('真实DOM', TDOM);
    debugger;//打断点
    console.log('虚拟DOM', VDOM);//是一个对象
    console.log(typeof VDOM);
  </script>

image.png

JSX

1. 全称:  JavaScript XML

2. react定义的一种类似于XML的JS扩展语法: JS + XML本质是React . createElement ( component , props , ... children ) 方法的语法糖

3. 作用: 用来简化创建虚拟DOM

1) 写法:var ele = <h1>Hello JSX!</h1>

2) 注意1:它不是字符串, 也不是HTML/XML标签

3) 注意2:它最终产生的就是一个JS对象

JSX小练习

 <!-- 准备好一个容器 -->
  <div id="test"></div>
  <!-- 引入React核心库 全局有React-->
  <script src="../js/react.development.js"></script>
  <!-- 引入React扩展库 全局有React DOM-->
  <script src="../js/react-dom.development.js"></script>
  <!-- 引入babel -->
  <script src="../js/babel.min.js"></script>
  <script type="text/babel">
    //  区分js语句与js表达式
    // 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
    // 语句:if(){}判断语句 for(){}循环 switch(){}
    const data = ['苹果', '橘子', '香蕉']
    //  创建虚拟dom
    const VDOM = (
      <div>
        <h1>水果</h1>
        <ul>
          {
            data.map(item => (<li key="item">{item}</li>))
          }
        </ul>
      </div>
    )
    ReactDOM.render(VDOM, document.getElementById('test'))

模块与组件,模块化与组件化

模块

  1. 向外提供特定功能的js程序,一般就是一个js文件
  2. 随着业务逻辑增加,代码越来越多功能越来越复杂,不便于复用js,模块化会简化js的编写,提高js的运行效率 组件
  3. 用来实现局部功能效果的代码和资源的集合包括(html/css/js/image)等
  4. 实现一个界面功能的复用
  5. 复用编码,简化项目编码,提高运行效率 模块化 当应用的js都以模块来编写的, 这个应用就是一个模块化的应用,根据js功能划分模块 组件化 应用是以组件的方式实现,实现ui结构的复用,这个应用就是组件化的应用

React面向组件编程

函数式组件:用函数定义的组件,无state(状态)

注意:

  1. 首字母大写

  2. ReactDOM.render()第一个参数必须是 组件

  3. 函数必须要有返回值 渲染组件到页面发生了什么?

  4. React解析组件标签,找到了Demo组件

  5. 发现组件是使用函数调用的,调用该函数,将返回的虚拟dom转为真实dom,最后展示到页面中

     <script type="text/babel">
        //  创建函数式组件
        function Demo() {
        console.log(this)//undefined因为babel编译后开启了严格模式
          return '我是函数式组件'
        }
        // 渲染组件到页面
        
        ReactDOM.render(<Demo />, document.getElementById('test'))
  </script>

类式组件:用类定义的组件,有state(状态)

render中的this是谁? Demo组件的实例对象 注意:

  1. 必须继承React.component
  2. 必须有render渲染
  3. 必须有return返回值 ReactDOM.render发生了什么?
  4. 发现组件是使用类定义的,随后new出该类的实例,并通过该实例调用到原型上的render方法
  5. 将 render返回的虚拟dom转为真实dom展示在页面上 总结:
  6. 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性的时候才写
  7. 如果A继承了B,且A中写了构造器,那么A类构造器中的super是必须要调用的
  8. 类中所定义的方法,都放在了原型对象上,供实例使用
    <script type="text/babel">
        //  创建类组件
        class Demo extends React.Component{
            //render是放在Demo的原型对象上的,供实例使用
          render(){
              return '我是类组件'
          }
        }
        //渲染组件到页面(上面的render和下面的不是一会事)
        //
        ReactDOM.render(<Demo />, document.getElementById('test'))

组件实例的核心属性state(state位于组件的实例对象身上)

state属性的基本使用

<script type="text/babel">
    //  创建函数式组件
    class Weather extends React.Component {
      constructor(props) {
        super(props)
        this.state = {
          HOT: true,
          People: '东京很炎热'
        }
      }
      render() {
        // wether组件的实例对象
        console.log(this);
        return <h2>今天的天气{this.state.HOT ? this.state.People : '不行啦'}</h2>
      }
    }
    //  渲染组件到页面
    ReactDOM.render(<Weather />, document.getElementById('test'))
  </script>

React调用函数的方法初步

<script type="text/babel">
    // 真实情况这么写太繁琐
    let that
    //  创建函数式组件
    class Weather extends React.Component {
      constructor(props) {
        super(props)
        that = this
        this.state = {
          HOT: true,
          People: '东京很炎热'
        }
      }
      render() {
        // wether组件的实例对象
        console.log(this);
        return <h2>今天的天气{this.state.HOT ? this.state.People : '不行啦'}</h2>
      }
    }
    // React调用函数(写法1)
    function demo() {
      console.log('因为东京很热,所以穿的少');
      // 获取不到this-》undefined
      console.log(that.state.HOT);
    }
    //  渲染组件到页面
    ReactDOM.render(<Weather />, document.getElementById('test'))
  </script>

React调用函数的方法具体实现

//  创建函数式组件
    class Weather extends React.Component {
      constructor(props) {
        super(props)
        //改变this指向解决获取不到this问题
        this.demo=this.demo.bind(this)//成功指向了weather的实例对象 
        // 初始化状态
        this.state = {
          HOT: true,
          People: '东京很炎热'
        }
      }
      render() {
        // 读取状态
        const { HOT } = this.state.HOT
        const { People } = this.state.People
        // wether组件的实例对象
        console.log(this);

        // onClick={demo()}把demo调用的返回值赋给onClick所以直接获取结果,没有点击过程,所以不要调用函数
        return <h2 id="title" onClick={this.demo}>今天的天气{this.state.HOT ? this.state.People : '不行啦'}</h2>
      }
      // 写法2
      demo() {
        // demo放在哪里?->放在Weather的原型对象上,供实例使用
        // demo是onClick的回调,不是实例调用的是直接调用的
        // 类中的方法默认开启局部严格模式所以this为undefined
        console.log('因为东京很热,所以穿的少');
        // 获取不到this-》undefined
        console.log(this.state.HOT);
        
      }
    }
    //  渲染组件到页面
    ReactDOM.render(<Weather />, document.getElementById('test'))

setState的使用

状态中的数据不能直接修改,需要用到setState内置API修改

在上面的写法2的demo中写setState修改HOT的值(最基本的修改)

    Const HOT = this.state.HOT
    //状态是通过stateSate进行更新,而且更新是一种合并,不是更换
    this.setState({HOT:!HOT})

上面的方式是告诉我们react是怎么来的,但是实际开发中不会接触到构造器这些方法,只是更方便,更具体的让我们了解react,使用构造器会遇到写多少个方法就要在构造器中改变(调用)多少次的问题

下面来了解一下react的精简写法

     //  创建组件
    class Weather extends React.Component {
      constructor(props) {
        super(props)
      }
      //初始化状态,将state放在了组件的实例对象身上
      state = { flag: false, People: '东京很炎热' }
      render() {
        const { flag, People } = this.state
        return <h1 onClick={this.changeHot}>{flag ? People : '不行啦'}</h1>
      }
      // 将方法放在实例的身上相当于赋值语句
      // 使用箭头函数的原因,箭头函数没有自身的this,所以放在了Weather组件的实例对象身上
      // 不要向之前一样将方法放在原型身上不被允许
      changeHot=()=> {
        const People = this.state.People
        this.setState({ flag: !flag })
      }
    }

state总结

  1. state是组件对象最重要的属性,值可以是对象{}可以包含多个键值对

  2. 通过更新组件的state来实现页面的重新渲染

  3. 组件中render方法中的this为组件实例对象

  4. 组件自定义的方法中的this为undefined

    • 通过bind改变this指向(在构造器中使用)
    • 箭头函数(赋值语句添加到组件实例对象身上)
  5. 状态数据不能直接修改或更新,需要使用steState方法进行修改

组件中的props属性

class People extends React.Component {
      render() {
        console.log(this);
        return (
          <ul>
            <li>姓名:{this.props.name}</li>
            <li>性别:</li>
            <li>年龄:{this.props.age}</li>
          </ul>
        )
      }
    }
    //1.
    ReactDOM.render(<People name="tom" age="18" />, document.getElementById('test'))
    
     const per = {name:'张三',age:15,sex:'女'}
    ReactDOM.render(<People name={per.name} age={per.age} />, document.getElementById('test'))
    //2.批量传递标签的属性
    //注意这里的{...per}不是浅拷贝的基本用法{}是react解析变量的分隔符它实际上是...per,主要是babel降级后可解析对象,但是不能随意使用,仅适用标签属性的数据
    ReactDOM.render(<People {...per} />, document.getElementById('test'))


image.png

image.png

上面提到了展开运算符,他都有什么具体应用呢?回顾一下

  1. 连接两个数组 [...arr1,...arr2]

  2. 在函数中传递参数

  3. 浅拷贝的基本用法{...obj}

  4. 复制对象的同时修改他的属性 const per = {name:'张三',age:15,sex:'女'} let per2 = {...per,name:'李四'} 对传入props标签类型以及数据的必要性进行限制

  5. 属性限定:

    • Person.propTypes={PropTypes.类型},注意限制函数类型需要使用func
    • isRequired表示必填项
  6. 默认值:Personaxios.defaultProps={key:value}如果参数不存在会显示默认值

 <!-- 准备好一个容器 -->
  <div id="test"></div>
  <!-- 引入React核心库 全局有React-->
  <script src="../js/react.development.js"></script>
  <!-- 引入React扩展库 全局有React DOM-->
  <script src="../js/react-dom.development.js"></script>
  <!-- 引入babel -->
  <script src="../js/babel.min.js"></script>
  <!-- 引入proptypes用于对标签属性进行限制 -->
  <script src="../js/prop-types.js"></script>
  <script type="text/babel">
    // 
    class People extends React.Component {
      render() {
        console.log(this);
        return (
          <ul>
            <li>姓名:{this.props.name}</li>
            <li>性别:</li>
            <li>年龄:{this.props.age}</li>
          </ul>
        )
      }
    }
    // 属性限定
    People.propTypes = {
      // React内置属性(16之后被弃用全都挂在react上会导致react很大)
      // name: React.PropTypes.string
      // 16之后
      // isRequired表示必填项
      // 注意限制函数类型需要使用func
      name: PropTypes.string.isRequired
    }
    // 默认值
    People.defaultProps={
      sex:'人妖'
    }
    // age="15"在上面参数相加的时候会变成 字符串拼接,如果需要变量要使用{参数}的形式
    ReactDOM.render(<People name="tom" age={15} />, document.getElementById('test'))
      </script>

props限制的简写方式

  1. props是只读的不能修改,但是运算可以,运算{age+1},修改 this.props.age = age + 1,但是直接修改的话会报错

既然可以把属性和方法放到组件的实例对象身上,那么我们可不可以把限制属性加在实例对象身上呢?


 class People extends React.Component {
     // 属性限定
   static  propTypes = {
      // React内置属性(16之后被弃用全都挂在react上会导致react很大)
      // name: React.PropTypes.string
      // 16之后
      // isRequired表示必填项
      // 注意限制函数类型需要使用func
      name: PropTypes.string.isRequired
    }
    // 默认值
    static defaultProps={
      sex:'人妖'
    }
      render() {
        console.log(this);
        return (
          <ul>
            <li>姓名:{this.props.name}</li>
            <li>性别:</li>
            <li>年龄:{this.props.age}</li>
          </ul>
        )
      }
    }
    
    // age="15"在上面参数相加的时候会变成 字符串拼接,如果需要变量要使用{参数}的形式
    ReactDOM.render(<People  age={15} />, document.getElementById('test'))//不传name会报错

image.png

image.png

类中的构造器与props

在构造器中传入props可以接收到传过来的数据,当然要用到super(props)对数据进行接收,但是我尝试了一下,只使用super()同样能接收到props中的数据,那么我干脆constructor中不传入props同样不报错

    class People extends React.Component{
        constructor(props){
            console.log(props)
            super(props)
        }
        //以下代码省略....
    }
    ReactDOM.render(<People name="jerry"/>,document.getElementById('test'))
    

image.png

用super()同样能接收到props中的数据

image.png

但是我不接不传,在类的实例对象身上拿不到props传的数据

image.png

image.png

那么构造器在类中有什么作用呢?我查了一下React的官网,它仅适用以下两种情况

  1. 通过给this.state复制对象来初始化内部state
  2. 为事件处理函数绑定实例

image.png

但是上述两种情况我们都可以使用赋值的方式放在类组件的实例对象身上来解决这个问题,所以使用构造器的意义不大

函数式组件使用props

在函数式组件中不考虑hooks的情况是没办法使用state和refs,因为函数式组件没有实例对象(this),但是它可以使用props,因为函数能够接收参数,函数组件进行类型限定需要放在函数的原型对象身上

      function Person(props) {
      console.log(this);---->undefined
      return (
        <ul>
          <li>姓名:{props.name}</li>
          <li>年龄:{props.age}</li>
          <li>性别:{props.sex}</li>
        </ul>
      )
    }
     // 属性限定
     People.propTypes = {
      // React内置属性(16之后被弃用全都挂在react上会导致react很大)
      // name: React.PropTypes.string
      // 16之后
      // isRequired表示必填项
      // 注意限制函数类型需要使用func
      name: PropTypes.string.isRequired
    }
    // 默认值
    People.defaultProps={
      sex:'人妖'
    }
    ReactDOM.render(<Person name="张三" age="18" sex="男" />, document.getElementById('test'))
 

image.png

React中的ref属性

字符串形式的ref:ref属性的基本使用,可以拿到DOM元素的节点,他拿到的是真实的DOM(已经不被react官方推荐使用,存在效率上的问题)

     //  创建组件
    class People extends React.Component {
      showData = () => {
        // 通过原生的方式拿到节点
        // const input = document.getElementById('input1')
        // alert(input.value)
        console.log(this);
        // 通过ref的方式拿到节点,拿到的不是虚拟dom而是转成真实dom后的节点
        console.log(this.refs.input1.value);
      }
      showData2 = () => {
        alert(this.refs.input2.value)
      }
      render() {
        console.log(this);

        return (
          <div>
            <input type="text" ref="input1" placeholder="点击按钮展示数据" />
            <button onClick={this.showData}>点我展示数据</button>
            <input type="text" ref="input2" onBlur={this.showData2} id="input1" placeholder="失去焦点示数据" />
          </div>
        )
      }
    }
    ReactDOM.render(<People />, document.getElementById('test'))

image.png

回调形式的ref

 //  创建组件
    class People extends React.Component {
      showData = () => {
        alert(this.input1.value)
      }
      showData2 = () => {
        const { input2 } = this
        alert(this.value)
      }
      render() {
        console.log(this);

        return (
          <div>
            <input type="text" ref={(currentNode) => { this.input1 = currentNode }} placeholder="点击按钮展示数据" />
            <button onClick={this.showData}>点我展示数据</button>
            <input type="text" ref={(currentNode) => { this.input2 = currentNode }} onBlur={this.showData2} id="input1" placeholder="失去焦点示数据" />
          </div>
        )
      }
    }
    ReactDOM.render(<People />, document.getElementById('test'))

createRef的使用

目前react最推荐使用的形式

 //  创建组件
    class People extends React.Component {
      // React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
      // 该ref只能存储一个,需要几个就创建几个React.createRef
      myRef = React.createRef()
      showData = () => {
        console.log(this.myRef.current.value);
        console.log(this.myRef);
      }

      render() {
        console.log(this);

        return (
          <div>
            <input type="text" ref={this.myRef} placeholder="点击按钮展示数据" />
            <button onClick={this.showData}>点我展示数据</button>

          </div>
        )
      }
    }
    ReactDOM.render(<People />, document.getElementById('test'))

image.png

React中的事件处理

//  创建组件
    class People extends React.Component {
      // 1.通过onXXX属性指定事件处理函数(注意大小写)
      // React使用的是自定义事件,而不是使用原生DOM事件->为了更好的兼容性

      // React中的事件是通过事件委托的方式处理的(委托给组件最外层的元素)
      //2.通过event.target得到发生事件的DOM元素对象  
      // 尽量减少refs的使用
      myRef = React.createRef()
      // 这个事件对象找的是button操作不到input所以不用event事件
      showData = () => {
        console.log(this.myRef.current.value);
        console.log(this.myRef);
      }
      // 用event处理数据避免使用refs
      // 这个event事件操作的是输入框
      showData2 = (event) => {
        console.log(event.target.value);
      }
      render() {
        console.log(this);

        return (
          <div>
            <input type="text" ref={this.myRef} placeholder="点击按钮展示数据" />
            <button onClick={this.showData}>点我展示数据</button>
            <input type="text" onBlur={this.showData2} />
          </div>
        )
      }
    }
    ReactDOM.render(<People />, document.getElementById('test'))

React中的非受控组件

    class Login extends React.Component {
      handleSubmit = (event) => {
        // 组织表单的默认事件
        event.preventDefault() 
        const { username, password } = this
        // 获取 的是节点要查他的value值才能获取数据
        alert(username.value)
      }
      render() {
        return (
          // 页面中输入类的dom是现用现取是非受控组件
          <form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
            用户名: <input type="text" name="username" ref={c => this.username = c} />
            密码: <input type="text" name="username" ref={c => this.password = c} />
            <button>登录</button>
          </form>
        )
      }
    }
    ReactDOM.render(<Login />, document.getElementById('test'))

受控组件

优势:能够省略掉ref推荐使用

    class Login extends React.Component {
      // 受控组件通过setState对数据进行维护,类似双向数据绑定(随着输入维护数据状态)
      // 初始化状态
      state = {
        username: '',
        password: ''
      }

      demo = (event) => {
        // 通过event事件对象获取
        console.log(event.target.value);
        // 用户名维护到状态里
        this.setState({ username: event.target.value })
      }
      handleSubmit = (event) => {
        // 组织表单的默认事件
        event.preventDefault()
        const {username,password} = this.state
        console.log(username);
      }
      render() {
        return (
          
          <form onSubmit={this.handleSubmit}>
            用户名: <input onChange={this.demo} type="text" name="username" />
            密码: <input type="text" name="password" />
            <button>登录</button>
          </form>
        )
      }
    }
    ReactDOM.render(<Login />, document.getElementById('test'))

函数柯里化

什么是高阶函数?

  1. 如果一个函数A,接受的参数是一个函数,这个A函数就可以说是一个高阶函数

  2. 若一个函数A,调用的返回值依然是一个函数,A就是一个高阶函数

常见的高阶函数:Promise 定时器 数组身上的方法

函数的柯里化:

通过函数调用继续返回函数的方式,实现多次结束参数最后统一处理的函数编码形式

class Login extends React.Component {
      // 初始化状态
      state = {
        username: '',
        password: ''
      }
      // 保存表单数据到状态中
      // saveFormDate就是高阶函数
      saveFormDate = (dataType) => {
        console.log(dataType);
        return (event) => {
          console.log(event.target.value);
          this.setState({[dataType]:event.target.value})
        }
      }
      handleSubmit = (event) => {
        // 组织表单的默认事件
        event.preventDefault()
        const { username, password } = this.state
        console.log(username);
      }
      render() {
        return (
          // 函数交给onchange作为事件的回调
          <form onSubmit={this.handleSubmit}>   
            用户名: <input onChange={this.saveFormDate('username')} type="text" name="username" />
            密码: <input type="text" name="password" onChange={this.saveFormDate('password')} />
            <button>登录</button>
          </form>
        )
      }
    }
    ReactDOM.render(<Login />, document.getElementById('test'))

函数柯里化的形式

    
    function sum(a){
        return(b)=>{
            return (c)=>{
                return a+b+c
            }
        }
    }
    const result = sum(1)(2)(3)

那上述形式可不可以不用柯里化解决呢?

  class Login extends React.Component {

      state = {
        username: '',
        password: ''
      }
      // 保存表单数据到状态中

      saveFormDate = (dataType, value) => {
        this.setState({ [dataType]: value })
      }
      handleSubmit = (event) => {
        // 组织表单的默认事件
        event.preventDefault()
        const { username, password } = this.state
        console.log(username);
      }
      render() {
        return (
          // onchange需要一个函数作为事件回调(不使用柯里化 )
          <form onSubmit={this.handleSubmit}>
            用户名: <input onChange={(event) => { this.saveFormDate('username', event.target.value) }} type="text" name="username" />
            密码: <input type="text" name="password" onChange={(event) => { this.saveFormDate('password', event.target.value) }} />
            <button>登录</button>
          </form>
        )
      }
    }
    ReactDOM.render(<Login />, document.getElementById('test'))

React生命周期

React的生命周期是React最重要的一个环节,我当时就没咋听懂,今天仔细总结一下

//  创建组件
    class Life extends React.Component {
      state = {
        opacity: 1
      }
      death = () => {
        // 法1清除定时器
        // clearInterval(this.timer)
        // 卸载组件
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
      }
      // 组件挂载页面之后调用只调用一次
      // componentDidMount是Life的实例调用的
      componentDidMount() {
        // 把定时器dom挂载到life的实例对象身上
        this.timer = setInterval(() => {
          // 获取原状态
          let { opacity } = this.state
          opacity -= 0.1
          if (opacity <= 0) opacity = 1
          this.setState({ opacity })
        }, 200)
      }
      // 组件将要卸载
      componentWillUnmount() {
        // 法2在生命周期清除定时器
        clearInterval(this.timer)
      }
      // render调用的时机:初始化渲染,状态更新之后
      render() {
        return (
          <div>
            <h2 style={{ opacity: this.state.opacity }}>React学不会怎么办</h2>
            <button onClick={this.death}>回家搬砖</button>
          </div>
        )
      }
    }
    ReactDOM.render(<Life />, document.getElementById('test'))

image.png

生命周期的三个阶段(旧)

  1. 初始化阶段:由ReactDOM.render()触发---初次渲染
  • constructor()

  • componentWillMount()

  • render()

  • componentDidMount()--->常用

    • 一般在这个钩子做一些初始化的操作
  1. 更新阶段由组件内部this.setSate()或父组件重新render触发
  •  shouldComponentUpdate()
  •  componentWillUpdate()
  • render()
  • componentDidUpdate()
  1. 卸载组件由ReactDOM.unmountComponentAtNode()触发
  •  componentWillUnmount()

    -一般做一些收尾工作

初始化时候的过程(挂载时)

 class Count extends React.Component {

      //构造器
      constructor(props) {
        console.log('Count---constructor');
        super(props)
        //初始化状态
        this.state = { count: 0 }
      }

      //加1按钮的回调
      add = () => {
        //获取原状态
        const { count } = this.state
        //更新状态
        this.setState({ count: count + 1 })
      }

      //组件将要挂载的钩子
      componentWillMount() {
        console.log('Count---componentWillMount');
      }

      //组件挂载完毕的钩子
      componentDidMount() {
        console.log('Count---componentDidMount');
      }

      //组件将要卸载的钩子
      componentWillUnmount() {
        console.log('Count---componentWillUnmount');
      }
      render() {
        console.log('Count-render');
        const { count } = this.state
        return (
          <div>
            <h2>当前求和为{count}</h2>
            <button onClick={this.add}>点我+1</button>
          </div>
        )
      }
    }
    ReactDOM.render(<Count />, document.getElementById('test'))

image.png

更新时的过程(setState)

  class Count extends React.Component {

      //构造器
      constructor(props) {
        console.log('Count---constructor');
        super(props)
        //初始化状态
        this.state = { count: 0 }
      }

      //加1按钮的回调
      add = () => {
        //获取原状态
        const { count } = this.state
        //更新状态
        this.setState({ count: count + 1 })
      }
      // 卸载组件按钮的回调
      death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
      }
      //组件将要挂载的钩子
      componentWillMount() {
        console.log('Count---componentWillMount');
      }

      //组件挂载完毕的钩子
      componentDidMount() {
        console.log('Count---componentDidMount');
      }

      //组件将要卸载的钩子
      componentWillUnmount() {
        console.log('Count---componentWillUnmount');
      }
      // 组件是否将要更新的钩子
      shouldComponentUpdate() {
        console.log('Count-shouldComponentUpdate');
        return true
      }
      // 组件将要更新的钩子
      componentWillUpdate() {
        console.log('Count-componentWillUpdate');
      }
      //组件更新完毕的钩子
      componentDidUpdate() {
        console.log('Count-componentDidUpdate');
      }
      render() {
        console.log('Count-render');
        const { count } = this.state
        return (
          <div>
            <h2>当前求和为{count}</h2>
            <button onClick={this.add}>点我+1</button>
            <button onClick={this.death}>卸载组件</button>
          </div>
        )
      }
    }
    ReactDOM.render(<Count />, document.getElementById('test'))

image.png

强制更新forceUpdate过程

强制更新不经过shouldComponentUpdate

     // 强制更新按钮的回调
      force = () => {
        this.forceUpdate()
      }
      
      <button onClick={this.force}>强制更新</button>

image.png

父组件render的过程

 class A extends React.Component {
      // 初始化数据
      state = { carName: '奔驰' }
      changeCar = () => {
        this.setState({ carName: '马自达' })
      }
      render() {
        return (
          <div>
            <div>A</div>
            <button onClick={this.changeCar}>换车</button>
            <B carName={this.state.carName} />
          </div>
        )
      }
    }
    class B extends React.Component {
      // 组件将要接收新的props的钩子
      // 第一次不会调用
      componentWillReceiveProps(props) {
        console.log('B---componentWillReceiveProps', props);
      }
      //       // 组件是否将要更新的钩子
      shouldComponentUpdate() {
        console.log('B-shouldComponentUpdate');
        return true
      }
      // 组件将要更新的钩子
      componentWillUpdate() {
        console.log('B---componentWillUpdate');
      }
      //组件更新完毕的钩子
      componentDidUpdate() {
        console.log('B---componentDidUpdate');
      }
      render() {
        console.log('B---render');
        return (
          <div>B组件接收到的参数{this.props.carName}</div>
        )
      }
    }
    ReactDOM.render(<A />, document.getElementById('test'))

image.png

生命周期有三条执行路线

image.png

生命周期的三个阶段(新)

  1. 初始化阶段: 由ReactDOM.render()触发---初次渲染

    1. constructor()
    2. getDerivedStateFromProps
    3. render()
    4. componentDidMount() =====> 常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

    1. getDerivedStateFromProps
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate
    5. componentDidUpdate()
  3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount() =====> 常用 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

旧的生命周期到新的生命周期过度删除了一些钩子,需要使用的话需要在前面加UNSAFE_,但是这几个钩子在实际中很少用到

  1. componentWillMount

  2. componentWillReceiveProps

  3. componentWillUpdate

重要的勾子

  1.  render:初始化渲染或更新渲染调用

  2.  componentDidMount:开启监听, 发送ajax请求

  3.  componentWillUnmount:做一些收尾工作, 如: 清理定时器 image.png

getDerivedStateFromProps()衍生状态 不常用没咋搞懂,截个官网的图,后期用到在补充,不过官网上的意思是state的值在任何时候都取决于props的时候使用

image.png

  <!-- 准备好一个“容器” -->
  <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>

  <script type="text/babel">
    //创建组件
    class Count extends React.Component {
     
      //构造器
      constructor(props) {
        console.log('Count---constructor');
        super(props)
        //初始化状态
        this.state = { count: 0 }
      }

      //加1按钮的回调
      add = () => {
        //获取原状态
        const { count } = this.state
        //更新状态
        this.setState({ count: count + 1 })
      }

      //卸载组件按钮的回调
      death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
      }

      //强制更新按钮的回调
      force = () => {
        this.forceUpdate()
      }

      //若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
      static getDerivedStateFromProps(props, state) {
        console.log('getDerivedStateFromProps', props, state);
        return null
      }

      //在更新之前获取快照,任何值都可以作为快照值(null,字符串,对象,函数)
      getSnapshotBeforeUpdate() {
        console.log('getSnapshotBeforeUpdate');
        return 'zhangsan'
      }

      //组件挂载完毕的钩子
      componentDidMount() {
        console.log('Count---componentDidMount');
      }

      //组件将要卸载的钩子
      componentWillUnmount() {
        console.log('Count---componentWillUnmount');
      }

      //控制组件更新的“阀门”
      shouldComponentUpdate() {
        console.log('Count---shouldComponentUpdate');
        return true
      }

      //组件更新完毕的钩子
      componentDidUpdate(preProps, preState, snapshotValue) {
        console.log('Count---componentDidUpdate', preProps, preState, snapshotValue);
      }

      render() {
        console.log('Count---render');
        const { count } = this.state
        return (
          <div>
            <h2>当前求和为:{count}</h2>
            <button onClick={this.add}>点我+1</button>
            <button onClick={this.death}>卸载组件</button>
            <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
          </div>
        )
      }
    }

    //渲染组件
    ReactDOM.render(<Count count={199} />, document.getElementById('test'))



挂载阶段触发

image.png

更新阶段触发

image.png

卸载阶段触发

image.png

强制更新触发

image.png

DOM的diffing

React中真实DOM都会对应一个虚拟DOM每次更新,本次的和上一次的虚拟DOM两者之间会进行比较,不变的不做修改,把新的内容映射到真实DOM上,diffing算法比较的最小单位是节点(标签),可能会遇到标签套标签的时候,diffing算法只更新变化那一个,因为如果全部更新的话,原来的数据就会丢失

虚拟DOM中key的作用

  1. key是虚拟DOM对象的标识,在更新显示时key具有重要的作用

  2. 当状态中的数据发生变化,react会根据新数据生成新的虚拟DOM随后React进行新虚拟DOM与旧虚拟DOM的diff比较

    • 旧虚拟dom中找到了与新虚拟dom相同的key

      • 若虚拟dom中内容没变,直接使用之前的真实dom
      • 若虚拟dom中内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom
    • 旧虚拟dom中未找到与新虚拟dom相同的key

      • 根据数据创建新的真实dom,随后渲染到页面 使用index作为key会引起的问题
  3. 若对数据进行:会造成逆序添加,逆序删除等破坏顺序的操作,如果你删除某个节点他的index会被下一个节点继承

    • 会产生没有必要的真实dom的更新=》界面效果可能没问题,但是效率低
  4. 如果结构中包含输入的DOM会产生DOM错误更新(input),界面效果产生问题(如果节点中还有数据会造成对比的节点中残留上个节点中的数据,造成顺序错乱)

  5. 不存在逆序添加,逆序删除等破坏顺序的操作,仅用于列表渲染不会造成其他问题

image.png

使用index作为key值会造成多次重复的虚拟DOM的更新,造成效率问题

image.png

使用唯一标识作为key值,虚拟DOM只会更新新增的数据,重复数据复用,提高效率

React基于脚手架搭建项目(webpack)

了解 :

  1. react提供创建react项目的脚手架库:create-react-app

  2. 项目整体架构:react + webpack + es6 + eslint

  3. 使用脚手架开发的特点:模块化,组件化,工程化

步骤:

  1. 安装 :npm i -g create-react-app

  2. 切换到项目目录 : create-react-app hello-react

  3. 进入项目文件夹: cd xxx

  4. 启动项目 : npm start

react 脚手架项目结构*

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库的支持)

index.html -------- 主页面配置信息 image.png

index.js - ------ 入口文件

image.png

注意:

  1. 使用React创建组件,要把一个组件放在他同名的文件夹下,包括他的样式结构,便于梳理组件的结构

  2. 创建的组件首字母需要大写

一个简单的react组件结构

一般把js结尾换成jsx结尾的,表示React的结构,这里使用的是js结尾,一般公司中也会把组件文件夹下的文件名换成index.xxx的格式方便了其他组件的引入

image.png

App.js

这里用的是React18.0的渲染方式,避免了控制台的错误

image.png

index.js

image.png

Hello.js

image.png

vscode插件

image.png

输入rcc一键生成类式组件

image.png

输入rfc一键生成函数式式组件

image.png

常用的代码片段快捷键

github.com/dsznajder/v…

react ajax

使用axios发起请求获取后台数据,会遇到跨域问题

import React, { Component } from 'react'
import axios from 'axios'
export default class index extends Component {
  getAxios = () => {
    axios.get('http://localhost:5000/students').then(
      response => {
        console.log(response.dat)
      },
      error => {
        console.log(error)
      }
    )
  }
  render() {
    return (
      <div>
        <button onClick={this.getAxios}>调用接口</button>
      </div>
    )
  }
}

image.png

解决:

  1. 使用proxy代理的方式解决

什么是proxy代理呢?当两个不同服务器的连接发送数据的时候会产生跨域问题,服务器会向另一个服务器发送请求,但是回传数据的时候会被axios拦截器拦截产生跨域,使用proxy代理会创建一个没有axios的中间件,两个服务器之间通过中间件发送和接收数据,不受axios的影响

//在package.json中
 // 配置你需要代理的本地连接
  "proxy":"http://localhost:5000"
//修改app.jsx中的axios请求地址
 axios.get('http://localhost:3000/students')

注意:中间件也是在3000服务器上的 image.png

  1. 涉及到多个服务器使用setupProxy的方法
  • 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  • 缺点:配置繁琐,前端请求资源时必须加前缀。
const proxy = require('http-proxy-middleware')

module.exports = function(app){
    app.use(
        proxy('/api1',{ //遇见/api1前缀的请求,就会触发该代理配置
                target:'http://localhost:5000', //请求转发给谁
                changeOrigin:true,//控制服务器收到的请求头中Host的值
                pathRewrite:{'^/api1':''} //重写请求路径(必须)
        }),
        proxy('/api2',{
                target:'http://localhost:5001',
                changeOrigin:true,
                pathRewrite:{'^/api2':''}
        }),
    )
}

组件之间传值

  1. 父子组件传值
  • 父传子:父组件向子组件传值需要定义一个函数或方法(可以是单个的数据),往子组件传递,子组件通过props接收
    //父传子
    export default class App extends Component {
      // 定义用户信息的数组
      state = {
        users: [],
        isFirst: true,
        isLoading: false,
        err: ''
      }
      // 渲染页面数据,子组件传过来的数据在父组件上修改
      getList = stateObj => {
        this.setState(stateObj)
      }
      render() {
        return (
          <div>
            <div className="container">
            //父组件向子组件传方法
              <Headers getList={this.getList} />
              //父组件向子组件传递数据
              <List {...this.state} />
            </div>
          </div>
        )
      }
    }
    //子接收
   const { users, isFirst, isLoading, err } = this.props

子传父:子组件向父组件传值,需要父组件向子组件传一个函数,子组件调用这个函数,在函数中传参,父组件会在函数的参数中接收到子组件传过来的值(接上父组件传方法,并进行数据修改)

   export default class Headers extends Component {
      // 获取输入框数据
      search = () => {
        // 获取用户输入
        const { value } = this.headRef
        console.log(value)
        //子组件调用父组件传过来的方法,通过参数的形式给父组件传值
        //发送请求前通知App更新状态
        this.props.getList({ isFirst: false, isLoading: true })
      }
      render() {
        return (
          <section className="jumbotron">
            <div>
              <input type="text" placeholder="enter the name you search" ref={c => (this.headRef = c)} />
              &nbsp;<button onClick={this.search}>Search</button>
            </div>
          </section>
        )
      }
    }
  1. 兄弟组件之间的传值

使用消息订阅-发布机制

  1.  工具库: PubSubJS

  2. 下载:npm i pubsub-js

  3. 使用

    • import PubSub from 'pubsub-js' //引入

    • PubSub.subscribe('token', (_, stateObj) => { this.setState(stateObj) }) //订阅

    •  PubSub.publish('delete', data) //发布消息

其实订阅发布模式,不仅仅适用于兄弟组件的传值,还可以进行跨组件间的传值,有点类似vue中的eventBus的效果

对SPA单页面应用的理解

  1. 整个应用只有一个页面
  2. 点击页面中的链接不会刷新页面,只会做页面的局部更新
  3. 数据都需要通过ajax请求获取,并在前端异步展现

对路由的理解

路径和组件的映射关系

  1.  浏览器端路由,value是component,用于展示页面内容。

  2.  注册路由: <Route path="/test" component={Test}>

  3.  工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

路由都是通过history实现的,而我们常用的是hash模式, History.createHashHistory() 另一种是H5自带的history身上的APIHistory.createBrowserHistory(),他是通过操作BOM实现的

React路由的使用

  1. 安装:npm i react-router-dom@5这是老版本的路由

  2. 引入:import { Link, BrowserRouter, Route , NavLink, Switch,Redirect} from 'react-router-dom'使用 <BrowserRouter> <App /></BrowserRouter>包裹App根组件统一路由器或者<HashRouter/>

  3. 使用 <Link to="/about">About</Link>包裹要跳转的链接

  4. 使用 <Route path="/about" component={About} />展示跳转链接的内容

  5. 使用NavLink包裹的内容相当于点谁给谁加上了 active效果

  6. NavLink的children属性是标签体内容 <NavLink>Home</NavLink>等价于<NavLink children="Home"/ > 所以父子传值的时候可以使用<NavLink {...this.props} />接收全部数据

  7. Switch适用于有多个相同的组件其中一个达到要求就不会展示另一个组件,提高匹配效率,匹配的还是path和component的对应关系

  8. 路由重定向,表示默认展示的内容,当其他组件没有匹配上的时候,默认展示重定向的内容 <Redirect to="/xxx"/>

路由组件与一般组件的区别

  1. 写法不同:

    • 一般组件 :<组件名/>
    • 路由组件:<Route path="/xxx" component={组件名}/>
  2. 文件存放位置不同

    • 一般组件 :components
    • 路由组件:pages
  3. 接收到的props不同:

    • 一般组件:写组件的时候传递了啥,就接受到啥

    • 路由组件:接收到三个固定的属性

      • history

      • loaction

      • match

路由的模糊匹配 与精确匹配

精确匹配:to要跳转的路径必须与path保持一致,否则匹配不到内容,在路由组件中使用exact表示开启严格模式

模糊匹配:to中的路径开头必须与路由组件中的path必须保持一致,其余可以不一致,路由组件会对其进行模糊匹配

image.png

路由嵌套

image.png

image.png

App.jsx

import React, { Component } from 'react'
import { Route, Redirect } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'
import Headers from './components/Headers'
import MyNavLink from './components/MyNavLink'
export default class App extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <Headers />
          </div>
        </div>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              <MyNavLink to="/about">About</MyNavLink>
              <MyNavLink to="/home">Home</MyNavLink>
              <Redirect to="/home" />
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                {/* 注册路由 */}
                <Route path="/about" component={About} />
                <Route path="/home" component={Home} />
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

Home/index.jsx

import React, { Component } from 'react'
import Message from './Message'
import News from './News'
import MyNavLink from '../../components/MyNavLink'
import { Route, Switch, Redirect } from 'react-router-dom'
export default class Home extends Component {
  render() {
    return (
      <div>
        <h2>Home组件内容</h2>
        <div>
          <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} />
          </Switch>
        </div>
      </div>
    )
  }
}

Home/Message/index.jsx

import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
  state = {
    messageArr: [
      { id: '01', title: '哈哈' },
      { id: '02', title: '不会' },
      { id: '03', title: '咋办' }
    ]
  }
  render() {
    const { messageArr } = this.state

    return (
      <div>
        <ul>
          {messageArr.map(item => {
            return (
              <li key={item.id}>
                <Link to="/home/message/detail">{item.title}</Link>
              </li>
            )
          })}
        </ul>
        <Route path="/home/message/detail" component={Detail} />
      </div>
    )
  }
}

MyNavLink/index.jsx

import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
  render() {
    return <NavLink className="list-group-item" {...this.props} />
  }
}

路由组件传参

  1. params传参
 {/* 向路由组件传params参数 (路由链接)*/}
<Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link>

{/* 声明接收params参数(注册路由) */}
 <Route path="/home/message/detail/:id:title" component={Detail} />
 
 在Detail组件中接收传递的参数
 
 this.props.match.params.id
  1. 向路由传递search参数
 {/* 向路由传递search参 数 */}
 <Link to={`/home/message/detail/?id=${item.id}&titlte =${item.title}`}>{ item.title}</Link>
  {/* 声明接收search参数(search参数无需声明接收) */}
  <Route path="/home/message/detail" component={Detail} />
   在Detail组件中接收传递的参数
   import qs from 'querystring'
   const { search } = this.props.location
   const { id, title } = qs.parse(search.slice(1))

image.png

  1. 向路由传递state参数

传递的state参数不会在地址栏中显示,刷新不会清除数据,因为BrowserRouter中的history.xxx会记录历史信息,如果清除浏览器缓存则会清除所有信息

     {/* 向路由传递state参数 */}
    <Link to={{ pathname: '/home/message/detail', state: { id: item.id, title: item.title } }}>
      {item.title}
    </Link>
    
 {/* 声明接收state参数(state参数无需声明接收) */}
<Route path="/home/message/detail" component={Detail} />
//接收state参数
const {id,title} = this.props.location.state || {}

路由的push与replace

push有缓存每次点击路径能留下历史记录能返回上一级(默认为push模式),replace是路由替换,每次点击路径会将上一次的路径替换,没有历史记录

编程式路由导航

this.props.history.push()

this.props.history.replace()

通过上面讲的传递路由参数相同的三种方式对路由进行传参跳转

withRouter

import {withRouter} from 'react-router-dom'使用withRouter能够使一般组件使用路由组件的API方法export default withRouter{组件名}

BrowserRouter与HashRouter的区别

image.png HashRouter #后面的内容不会传递给服务器,会产生历史记录,#可以理解为锚点,HashRouter没有用上history的API所有对state传过来的参数在浏览器刷新的时候不会保存

对redux的理解

学习文档

1. 英文文档: redux.js.org/

2. 中文文档: www.redux.org.cn/

3. Github: github.com/reactjs/red…

安装redux

npm i redux

redux工作流程 image.png

理解:store接收action传过来的状态指挥reducer干活

redux的三个核心概念

action

1. 动作的对象

2. 包含2个属性

l type:标识属性, 值为字符串, 唯一, 必要属性

l data:数据属性, 值类型任意, 可选属性

3. 例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

reducer

1. 用于初始化状态、加工状态。

2. 加工时,根据旧的state和action, 产生新的state的纯函数

store

1. 将state、action、reducer联系在一起的对象

2. 如何得到此对象?

1) import {createStore} from 'redux'

2) import reducer from './reducers'

3) const store = createStore(reducer)

3. 此对象的功能?

1) getState(): 得到state

2) dispatch(action): 分发action, 触发reducer调用, 产生新的state

3) subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

用redux实现一个简单的功能(不使用 action)

store.js

    // 引入createStore,专门用于创建redux中的store对象
    import {createStore} from 'redux'
    // 引入 为Count服务的reducer
    import countReducer from './count_reducer'

    export default createStore(countReducer)

count_reducer.js

// 用于创建一个为Count组件服务的reducer,本质是一个 函数
// reducer函数会接到两个参数,分别是之前的状态pre和动作对象action
const defaultState = 0
export default function countReducer(pre = defaultState, action) {
  console.log(pre, action)
  const { type, data } = action
  // 根据type决定如何加工数据
  switch (type) {
    case 'increment':
      return pre + data
    case 'decrement':
      return pre - data
    default:
      return defaultState
  }
}

Count/index.jsx

    import React, { Component } from 'react'
import store from '../../redux/store'
export default class Count extends Component {
  state = {
    count: 0
  }
  // 组件挂载的钩子
  componentDidMount() {
    // 检测redux中状态的变化,只要变化就调用render
    // 只要redux状态改变都会调用
    store.subscribe(() => {
      console.log('@')
      this.setState({})
    })
  }
  increment = () => {
    const { value } = this.selectNumber
    store.dispatch({ type: 'increment', data: value * 1 })
    // const { count } = this.state
    // this.setState({ count: count + value * 1 })
  }
  render() {
    return (
      <div>
        <h2>就和的值为:{store.getState()}</h2>
        <select ref={c => (this.selectNumber = c)}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <button onClick={this.increment}>+</button>&nbsp;
      </div>
    )
  }
}

在上面用到了store中提供的API

  1. store.getState()获取reducer中state的初始数据

  2. store.dispatch({ type: 'increment', data: value * 1 }),分发action,提供改变类型和状态

  3. store.subscribe()检测redux中状态的变化

备注:

  1. reducer可以初始化状态也可以改变状态,传递的初始状态是undefined

  2. redux只负责状态管理,状态改变驱动页面的展示需要自己触发

    • this.setState({}) 改变状态重新渲染render
    • store.subscribe(()=>{ReactDOM.render(,document.getElementById('root'))})在入口文件监听redux状态的改变渲染render,受diff算法的影响不会造成内存的损耗

用redux实现一个简单的功能(使用 action)

action.js

// 该文件专门为Count组件生成action对象

export const IncreateAction = data => ({ type: 'increment', data })

Count/index.jsx

import React, { Component } from 'react'
import store from '../../redux/store'
import { IncreateAction } from '../../redux/count_action'
export default class Count extends Component {
  state = {
    count: 0
  }
  // 组件挂载的钩子
  componentDidMount() {
    // 检测redux中状态的变化,只要变化就调用render
    // 只要redux状态改变都会调用
    store.subscribe(() => {
      console.log('@')
      this.setState({})
    })
  }
  increment = () => {
    const { value } = this.selectNumber
    // store.dispatch({ type: 'increment', data: value * 1 })
    store.dispatch(IncreateAction(value * 1))
    // const { count } = this.state
    // this.setState({ count: count + value * 1 })
  }
  render() {
    return (
      <div>
        <h2>就和的值为:{store.getState()}</h2>
        <select ref={c => (this.selectNumber = c)}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <button onClick={this.increment}>+</button>&nbsp;
      </div>
    )
  }
}

其余文件与上面相同

同步action与异步action

  1. 同步action就是指action的值为Object类型的一般对象

  2. 异步action,就是指action的值为函数

使用异步action需要使用中间件,因为store不能识别函数,只能识别一般对象,所以需要使用中间件处理

下载:npm i redux-thunk

在store.js中注入中间件

// 引入createStore,专门用于创建redux中的store对象
import { createStore,applyMiddleware } from 'redux'

// 引入 为Count服务的reducer
import countReducer from './count_reducer'
// 引入redux-thunk用于支持异步action
import thunk from 'redux-thunk '
export default createStore(countReducer,applyMiddleware(thunk ))

action.js

异步任务有结果以后需要同步分发(diapatch)一个action去操作真正的数据

// 异步action
export const createAsyncAction = (data, time) => {
  return () => {
    setTimeout((dispatch) => {
      // 通知修改
      dispatch(IncreateAction(data))
    }, time)
  }
}

Count/index.jsx

incrementAsync = () => {
    const { value } = this.selectNumber
    store.dispatch(createAsyncAction(value * 1, 500))
  }

react-redux

image.png

react-redux容器组件 和ui组件的基本写法(待优化)

containers/Count

//引入Count的UI组件
import CountUI from '../../components/Count'
//引入action
import {
	createIncrementAction,
	createDecrementAction,
	createIncrementAsyncAction
} from '../../redux/count_action'

//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'

/* 
	1.mapStateToProps函数返回的是一个对象;
	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
	3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
	return {count:state}
}

/* 
	1.mapDispatchToProps函数返回的是一个对象;
	2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
	3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
	return {
		jia:number => dispatch(createIncrementAction(number)),
		jian:number => dispatch(createDecrementAction(number)),
		jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
	}
}

//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)


简写,react-redux内部的Api处理自动分发action

export default connect(state=>({count:state}),{
    jia:createIncrementAction,
    jian:createDecrementAction,
    jiaAsync:createIncrementAsyncAction
})(CountUI)

components/Count/index.jsx(ui组件)

import React, { Component } from 'react'

export default class Count extends Component {

	//加法
	increment = ()=>{
		const {value} = this.selectNumber
		this.props.jia(value*1)
	}
	//减法
	decrement = ()=>{
		const {value} = this.selectNumber
		this.props.jian(value*1)
	}
	//奇数再加
	incrementIfOdd = ()=>{
		const {value} = this.selectNumber
		if(this.props.count % 2 !== 0){
			this.props.jia(value*1)
		}
	}
	//异步加
	incrementAsync = ()=>{
		const {value} = this.selectNumber
		this.props.jiaAsync(value*1,500)
	}

	render() {
		//console.log('UI组件接收到的props是',this.props);
		return (
			<div>
				<h1>当前求和为:{this.props.count}</h1>
				<select ref={c => this.selectNumber = c}>
					<option value="1">1</option>
					<option value="2">2</option>
					<option value="3">3</option>
				</select>&nbsp;
				<button onClick={this.increment}>+</button>&nbsp;
				<button onClick={this.decrement}>-</button>&nbsp;
				<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
				<button onClick={this.incrementAsync}>异步加</button>&nbsp;
			</div>
		)
	}
}

备注:容器组件中的store是靠props传进去的,不是在容器组件中直接引入,使用rect-redux后,不需要在入口文件检测redux状态的改变并渲染,react-redux中的connect已经做了这件事

react-redux实现数据共享(完整版)

  1. ui组件 conteiners/Count/index.jsx
import React, { Component } from 'react'

//引入Count的UI组件
// import CountUI from '../../components/Count'
//引入action
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/actions/count'

//引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
class Count extends Component {
  state = { carName: '奔驰c63' }

  //加法
  increment = () => {
    const { value } = this.selectNumber
    this.props.createIncrementAction(value * 1)
  }
  //减法
  decrement = () => {
    const { value } = this.selectNumber
    this.props.createDecrementAction(value * 1)
  }
  //奇数再加
  incrementIfOdd = () => {
    const { value } = this.selectNumber
    if (this.props.count % 2 !== 0) {
      this.props.createIncrementAction(value * 1)
    }
  }
  //异步加
  incrementAsync = () => {
    const { value } = this.selectNumber
    this.props.createIncrementAsyncAction(value * 1, 500)
  }

  render() {
    //console.log('UI组件接收到的props是',this.props);
    return (
      <div>
        <h1>当前求和为:{this.props.count}</h1>
        <select ref={c => (this.selectNumber = c)}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        &nbsp;
        <button onClick={this.increment}>+</button>&nbsp;
        <button onClick={this.decrement}>-</button>&nbsp;
        <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>&nbsp;
        <button onClick={this.incrementAsync}>异步加</button>&nbsp;
      </div>
    )
  }
}
    /* 
        1.mapStateToProps函数返回的是一个对象;
        2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
        3.mapStateToProps用于传递状态
    */
    // function mapStateToProps(state){
    // 	return {count:state}
    // }

    /* 
            1.mapDispatchToProps函数返回的是一个对象;
            2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
            3.mapDispatchToProps用于传递操作状态的方法.
    */
    // function mapDispatchToProps(dispatch){
    // 	return {
    // 		jia:number => dispatch(createIncrementAction(number)),
    // 		jian:number => dispatch(createDecrementAction(number)),
    // 		jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
    // 	}
    // }

    //使用connect()()创建并暴露一个Count的容器组件
    // export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
    export default connect(state => ({ count: state.countReducer }), {
       createIncrementAction,
       createDecrementAction,
       createIncrementAsyncAction
    })(Count)

containers/Person/index.jsx

import React, { Component } from 'react'
import { nanoid } from 'nanoid'
// 引入connect
import { connect } from 'react-redux'
// 引入加人的action
import { person } from '../../redux/actions/person'
class Person extends Component {
  addPerson = () => {
    const name = this.nameNode.value
    const age = this.ageNode.value
    // console.log(name, age)
    const personObj = { id: nanoid(), name, age }
    this.props.addPersons(personObj)
  }
  render() {
    return (
      <div>
        <h2>Person组件,求和的数值{this.props.he}</h2>
        <input type="text" ref={c => (this.nameNode = c)} />
        <input type="text" ref={c => (this.ageNode = c)} />
        <button onClick={this.addPerson}>添加</button>
        <ul>
          {this.props.persons.map(item => (
            <li key={item.id}>
              {item.name}---{item.age}
            </li>
          ))}
        </ul>
      </div>
    )
  }
}
export default connect(state => ({ persons: state.personReducer, he: state.countReducer }), {
  addPersons: person
})(Person)


  1. redux文件

actions/count.js

/* 
	该文件专门为Count组件生成action对象
*/
import { INCREMENT, DECREMENT } from '../constant'

//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })

//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data, time) => {
  return dispatch => {
    setTimeout(() => {
      dispatch(createIncrementAction(data))
    }, time)
  }
}

actions/person.js

import { ADDPERSON } from '../constant'
export const person = personObj => ({ type: ADDPERSON, data: personObj })

reducers/count.js

/* 
1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
import { INCREMENT, DECREMENT } from '../constant'

const initState = 0 //初始化状态
export default function countReducer(preState = initState, action) {
  // console.log(preState);
  //从action对象中获取:type、data
  const { type, data } = action
  //根据type决定如何加工数据
  switch (type) {
    case INCREMENT: //如果是加
      return preState + data
    case DECREMENT: //若果是减
      return preState - data
    default:
      return preState
  }
}

reducers/person.js

import { ADDPERSON } from '../constant'

const initState = [{ id: '001', name: 'zs', age: 18 }]
export default function personReducer(pre = initState, action) {
  const { type, data } = action
  switch (type) {
    case ADDPERSON:
      return [data, ...pre]
    default:
      return pre
  }
}

constant.js

/* 
该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADDPERSON = 'addperson'

store.js

/* 
	该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/

//引入createStore,专门用于创建redux中最为核心的store对象
// 引入 combineReducers用于和并多个reducer
import { createStore, applyMiddleware, combineReducers } from 'redux'
//引入为Count组件服务的reducer
import countReducer from './reducers/count'
// 引入 Person的reducer
import personReducer from './reducers/person'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
// redux为我们保存的状态是一个对象combineReducers传入的对象就是redux保存的对象
const allReducer = combineReducers({  countReducer, personReducer })
//暴露store
export default createStore(allReducer, applyMiddleware(thunk))

App.jsx

import React, { Component } from 'react'
import Count from './containers/Count'
import Person from './containers/Person'
import store from './redux/store'

export default class App extends Component {
  render() {
    return (
      <div>
        {/* 给容器组件传递store */}
        <Count store={store} />
        <Person store={store} />
      </div>
    )
  }
}

index.js

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import store from './redux/store'
import { Provider } from 'react-redux'
ReactDOM.createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
)

Provider的使用

每个容器组件都需要传一个store比较繁琐,所以可以使用react-redux提供的API Provider对所有需要store的容器组件提供

使用方法:

import store from './redux/store'
import { Provider } from 'react-redux'
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,

  document.getElementById('root')
)

纯函数的概念

  1. 只要是同样的输入(实参),必定得到同样的输出(返回)
  2. 纯函数要求不得改写参数数据,不能产生任何副作用,比如网络请求
  3. 不能调用Date.now()或者Math.random()等不纯方法
  4. redux的reducer函数必须是一个纯函数

react进阶

路由组件的lazyLoad

    import React ,{component,lazy} from 'react'
    //1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
    const Login = lazy(()=>import('@/pages/Login'))

    //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
    <Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>

Hooks

  1. 什么是Hook? hook是React 16.8版本增加的新特性,可以在函数组件中使用state以及其他的React特性

  2. 三个常用的Hook

    • state Hook :React.useState()
    • Effect Hook: React.useEffect()
    • Ref Hook: React.useRef()
  3. State Hook

    • State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
    • 语法: const [xxx, setXxx] = React.useState(initValue)
    • useState()说明:
      • 参数: 第一次初始化指定的值在内部作缓存
      • 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
    • setXxx()2种写法:
      • setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
      • setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
  4. Effect Hook

    • Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

    • React中的副作用操作: 发ajax请求数据获取 设置订阅 / 启动定时器 手动更改真实DOM

    • 语法和说明:

      useEffect(() => { 
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
    
    • 备注:其中不写数组表示任何操作都检测,写一个空数组表示任何操作都不检测,数组中写参数,表示检测你写入参数的操作
    • 可以把 useEffect Hook 看做如下三个函数的组合 componentDidMount() componentDidUpdate() componentWillUnmount()
  5. Ref Hook

    • Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
    • 语法: const refContainer = useRef()
    • 作用:保存标签对象,功能与React.createRef()一样
  6. 使用demo

import React from 'react'
import ReactDOM from 'react-dom/client'
function App() {
  const [count, setCount] = React.useState(0)
  React.useEffect(() => {
    console.log('@')
    let timer = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
    // 回调相当于componentWillUnmount
    return () => {
      clearInterval(timer)
    }
  }, [count])
  const myRef = React.useRef()
  function add() {
    setCount(count => count + 1)
  }
  function del() {
    ReactDOM.unmountComponentAtNode(document.getElementById('root'))
  }
  function show() {
    alert(myRef.current.value)
  }
  return (
    <div>
      <h2>当前count求和为{count}</h2>
      <input type="text" ref={myRef} />
      <button onClick={add}>点击+1</button>
      <button onClick={del}>点击卸载组件</button>
      <button onClick={show}>点击提示</button>
    </div>
  )
}

export default App

Context

  1. 创建Context容器对象: const XxxContext = React.createContext()

  2. 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据: <xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>

  3. 后代组件读取数据:

    第一种方式:仅适用于类组件 static contextType = xxxContext // 声明接收context this.context// 读取context中的value数据

    第二种方式: 函数组件与类组件都可以

    <xxxContext.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>

import React, { Component } from 'react'
const MyContext = React.createContext()
const { Provider, Consumer } = MyContext
export default class A extends Component {
  state = {
    userName: 'tom',
    age: 18
  }
  render() {
    const { userName, age } = this.state
    return (
      <div>
        <h3>我是A组件</h3>
        <h4>我的用户名是{userName}</h4>
        <Provider value={{ userName, age }}>
          <B />
        </Provider>
      </div>
    )
  }
}

class B extends Component {
  render() {
    return (
      <div>
        <h3>我是B组件</h3>
        <C />
      </div>
    )
  }
}

class C extends Component {
  static contextType = MyContext
  render() {
    console.log(this.context)
    const { userName, age } = this.context
    return (
      <div>
        <h3>我是C组件</h3>
        <ul>
          <li>{userName}</li>
          <li>{age}</li>
        </ul>
      </div>
    )
  }
}
//第二种方法,函数组件类组件都可以使用(Consumer)
function C() {
  return (
    <div>
      <h3>我是C组件</h3>
      <Consumer>
        {value => {
          console.log(value)
          return `${value.userName}-${value.age}`
        }}
      </Consumer>
    </div>
  )
}

备注:使用hook提供的上下文关系对象useContext()能直接使用自定义上下文对象中Provider传过来的value值


import React, { useContext, useEffect, useState } from 'react'

const value = useContext(appContext)

return (
    <div>
      <p>{value.username}</p>
    </div>
)