React及JSX语法糖

628 阅读22分钟

react基础

1.react简介

1.1 React 介绍

1.1.1 React是什么

  1. React 是一个用于构建用户界面(HTML 页面)的 JavaScript 库
  2. 如果从mvc的角度来看,React仅仅是视图层(V)的解决方案。也就是只负责视图的渲染,并非提供了完整了M和C的功能
  3. 参考网址:react官网(reactjs.org/) ,react中文网(zh-hans.reactjs.org/)

1.1.2 React特点

  1. 组件化:组件用于表示页面中的部分内容,多个组件组合复用即可实现完整页面功能,是react中的核心内容之一
  2. 声明式UI:可直接在js文件中描述html结构
  3. 应用范围广:可开发web应用 / 移动端应用 / VR应用等

1.1.3 React 脚手架创建项目

  1. 原始方式(不推荐)

    //全局安装脚手架模块包 ,如已有则不用重复安装模块包
    npm i -g create-react-app
    //通过命令创建react项目 项目名不能含中文
    create-react-app xxx
    
  2. 新方式(不用担心脚手架模块包版本问题,自动使用最新版)

    //npx是node提供的命令,命令执行流程:自动下载脚手架模块包-->创建相应项目-->自动卸载脚手架模块包
    npx create-react-app xxx
    

1.1.4 React的基本使用

// 1.引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';

// 2.调用方法创建元素React.createElement('标签名',标签的属性对象,标签内容)
const title = React.createElement('h2', { id: 'test' }, 'helloReact');
const contest = React.createElement('a',{ href: 'http://www.baidu.com' },'跳转到百度');

// 3.调用方法渲染虚拟dom, ReactDOM.render(创建的虚拟dom ,要挂载的 真实存在的dom)
// 注意:ReactDOM.render方法不能调用多次,会覆盖
ReactDOM.render(title, document.querySelector('#root'));
ReactDOM.render(contest, document.querySelector('#root')); //最终页面上会只显示这个

2. JSX语法糖

2.1 JSX简介

  1. jsx本质上就是React.createElement的语法糖,不在使用React.createElement的繁琐写法,而是使用更直观的声明式语法,与HTML结构相同,降低了react的学习成本,提高了开发效率

  2. 基本使用体验

    // 引入依赖包
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    // jsx语法糖 用于简化React.createElement()的写法
    const ulNode = (
      <div>
        <ul>
          <li>香蕉</li>
          <li>火龙果</li>
          <li>荔枝</li>
        </ul>
      </div>
    );
    
    ReactDOM.render(ulNode, document.querySelector('#root'));
    

2.2 JSX 注意点

  1. 只有在脚手架中才能使用 jsx 语法,JSX需要经过 babel 的编译处理,才能在浏览器中使用。脚手架中已经默认有了这个配置。
  2. JSX必须要有一个根节点, <></> <React.Fragment></React.Fragment>
  3. 元素必须有闭合标记</>
  4. JSX可以换行,如果JSX有多行,推荐使用()包裹JSX,防止自动插入分号的bug
  5. 标签属性与js关键词冲突要做替换:
    • class =====> className
    • for========> htmlFor

2.3JSX插值表达式

  1. 作用:使标签中可以使用表达式,{表达式}

  2. 插值表达式 - 基础数据类型

    // 引入依赖包
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    // 当表达式为 布尔类型 / null / undefined 时,标签结构会生成,但不会显示在页面上
    const test1 = '测试内容';
    const test2 = 123;
    const test3 = true;
    const test4 = null;
    const test5 = undefined;
    const xmlNode = (
      <div>
        <h2>{test1}</h2>
        <h2>{test2}</h2>
        <h2>{test3}</h2>
        <h2>{test4}</h2>
        <h2>{test5}</h2>
      </div>
    );
    
    //要挂载到真实存在的dom元素上
    ReactDOM.render(xmlNode, document.querySelector('#root'));
    
    
  3. 插值表达式 - 数组

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    // 数组的每一项元素会在插值中自动作为一个节点
    let list = [<div>1</div>, <div>2</div>, <div>3</div>];
    let list2 = [1, 2, 3];
    const xmlNode = (
      <div>
        <div>{list}</div>
        <h2>{list2}</h2>
      </div>
    );
    ReactDOM.render(xmlNode, document.querySelector('#root'));
    
  4. 插值表达式 - 对象

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    // 对象本身不能直接放在插值表达式中,如果属性值不是对象,可以用 对象.属性 语法用在插值表达式
    const person = {
      name: '1354',
      age: 5,
    };
    const xmlNode = (
      <div>
            <h3>{person.age}</h3>
      </div>
    );
    ReactDOM.render(xmlNode, document.querySelector('#root'));
    
  5. 插值表达式 - 函数

    // 引入依赖包
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    //声明变量接收的函数需先声明才能使用,
    //如果函数返回值是个对象,也可以使用 {testFn().属性名}的方式用在插值表达式中
    const testFn = () => {
      return {
        name: '5465',
      };
    };
    
    const xmlNode = (
      <div>
        <h4>{testFn().name}</h4>
        <h4>{tFN().msg}</h4>
      </div>
    );
    
    function tFN() {
      return {
        msg: '陌上花开,可缓缓归矣',
      };
    }
    
    ReactDOM.render(xmlNode, document.querySelector('#root'));
    
  6. 插值表达式 - jsx本身

    // 引入依赖包
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    //变量可以接收一段jsx文本,然后用在插值表达式中
    const jsxNode = <h1>一段jsx文本</h1>;
    const xmlNode = (
      <div>
        {jsxNode}
      </div>
    );
    ReactDOM.render(xmlNode, document.querySelector('#root'));
    
  7. 插值表达式 - 三元表达式与逻辑运算符&&

    
    // 引入依赖包
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    const age=19
    const jsxNode = age > 18 ? <h2>已经成年啦</h2> : <h3>还是小朋友</h3>;
    const ulNode = (
      <div>
        {age > 18 ? <h2>已经成年啦</h2> : <h3>还是小朋友</h3>}
        {age > 18 && <h2>已经成年啦</h2>}
        {jsxNode}
      </div>
    );
    
    ReactDOM.render(ulNode, document.querySelector('#root'));
    

2.4JSX条件渲染

// 引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';

/* 条件渲染
  1.三元
  2.ifelse --->要通过函数返回值的方式才能使用ifelse做条件渲染
  3.逻辑运算符&&
*/
const isshow = true;
const testFn = () => {
  if (isshow) {
    return <h2>加载完成</h2>;
  } else {
    return false;
  }
};
const testFn1 = () => {
  return isshow ? <h2>加载完成</h2> : false;
};
const testFn2 = () => {
  return isshow && <h2>加载完成</h2>;
};

const xmlNode = (
  <div>
    {testFn()}
    {testFn1()}
    {testFn2()}
  </div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));

2.5列表-数组渲染

// 引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';

const list = ['zs', 'ls', 'lb', 'df'];
const linode = list.map((item, index) => <li key={index}>{item}</li>);

const xmlNode = (
  <div>
    <ul>{linode}</ul>
                            
    {/* 可以直接将map写在插值中 */}
    <ul>
      {list.map((item, index) => (
        <li key={index}>第二遍:{item}</li>
      ))}
    </ul>
  </div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));

2.6 JSX动态样式

// 引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';

import './indexcopy/base.css';

/* 样式
  1.行内style:{{CSS属性名:CSS属性值,CSS属性名:CSS属性值}}
  2.引入外部样式文件
 */
const sizec = 48;//jsx中 px 单位可以省略
const colorc = '#fff';
const xmlNode = (
  <div>
    <ul className="list" style={{ fontSize: sizec, color: colorc }}>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
  </div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));

2.7函数式组件

  1. 函数组件本质上也是JS函数,只需要在函数中返回一段jsx,
  2. 函数名必须以大写开头,才能以函数名为标签使用,否则会报错
  3. 也可以将别的组件以标签的形式嵌套在组件内
// 引入依赖包
import React from 'react';
import ReactDOM from 'react-dom';

const SHe = () => (
  <div>
    组件一
    <h1>君不见黄河之水天上来</h1>
  </div>
);
const SayHello = () => (
  <div>
    组件测试
    <h1>君不见高堂明镜悲白发</h1>
    {/* 也可以将别的组件以标签的形式嵌套在组件内 */}
    <SHe></SHe>
  </div>
);
const xmlNode = (
  <div>
    <SayHello></SayHello>
  </div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));

2.8class组件

  1. class组件的实质是使用class构造函数去继承react中封装好的 React.Component的对象方法等
// import React from 'react'; --->方式一 不解构引入依赖包
import { Component } from 'react'; //方式二 解构引入依赖包
import ReactDOM from 'react-dom';

/* 方式一  React.Component
class PerNode extends React.Component {
  render() {
    return <h2>我是一个类组件</h2>;
  }
} */
// 方式二 直接使用解构后的 Component 
class PerNode extends Component {
  render() {
    return <h2>我是一个类组件</h2>;
  }
}

const xmlNode = (
  <div>
    <PerNode></PerNode>
  </div>
);
ReactDOM.render(xmlNode, document.querySelector('#root'));

2.9 React事件

  1. 绑定事件:on+驼峰事件名={事件处理函数名}

    import React, { Component } from 'react';
    export default class App extends Component {
      render() {
        return (
          <div>
            {/* 少量代码可以直接写在箭头函数里面 */}
            <button onClick={() => alert('测试一')}>点击测试一</button>
            {/* 多行代码可以在 render同级处声明函数,通过this调用,不行写成 this.XXXFn() ,要去掉() */}
            <button onClick={this.handleClick}>点击测试二</button>
          </div>
        );
      }
      handleClick() {
        alert('测试二');
      }
    }
    
  2. 获取事件对象与事件传参

    /*通过事件函数的默认的形参, 获取事件对象*/
    import React, { Component } from 'react';
    
    export default class App extends Component {
      render() {
        return (
          <div> 
            {/* 通常加上一个箭头, 在箭头函数的方法体内主动传参 */}
            <a href="http://www.baidu.com" onClick={(e) => this.handleClick(e, '123')} >点我传参</a>
          </div>
        );
      }
      handleClick(e, msg) {
        console.log('e  ----->  ', e);
        console.log('msg  ----->  ', msg);
        e.preventDefault();
      }
    }
    
  3. React中关于this的两种执行情况

    /*react中的this分为两种请求
      情况1: React自带结构体中, this指向组件实例对象 ✅ 如render函数、
      情况2: React非自带结构体中, this执行undefined 🔔 往往需要自行处理
    */
    import React, { Component } from 'react';
    
    export default class App extends Component {
      render() {
        console.log('this  ----->  ', this);//组件实例
        return (
          <div>
            <button onClick={this.handleXxxxx}>点我查看this</button>
          </div>
        );
      }
        
      handleXxxxx() {
        console.log('this  ----->  ', this);//undefined
      }
    }
    
  4. 关于this的解决方案

    /*解决this指向undefined的常用办法(两种)
      原理: 箭头函数没有this, 会自动绑定环境中的this
      1. 在render函数内, 事件箭头函数绑定事件
      2. 在class内, 用箭头函数定义事件
    */
    
    import React, { Component } from 'react';
    
    export default class App extends Component {
      state = {
        msg: '123',
      };
    
      render() {
        return (
          <div>
            <button
              //  1. 在render函数内事件箭头函数绑定事件
              // onClick={() => this.handleXxxxx()}
              onClick={this.handleXxxxx}
            >
              点我查看this
            </button>
          </div>
        );
      }
    
      // 2. 定义事件函数时, 用箭头函数定义事件
      handleXxxxx = () => {
        console.log('this  ----->  ', this);
      };
    }
    
    

2.10React状态

  1. React不允许直接修改state的值, 不能用会会改变原始数据的方法,如: push concat。(推荐: 新值覆盖旧值)
  2. 在React中引起界面变化唯一的方法就是setState

import React, { Component } from 'react';
export default class App extends Component {
  state = {
    count: 100,
    list: [1, 2, 3],
  };

  handleAdd = () => {
    // ✅ 使用新值覆盖旧值
    // this.setState({ count: this.state.count + 1 });

    // ✅
    this.setState({
      list: [...this.state.list, 4],
    });

    // console.log('this.state.count  ----->  ', this.state.count);
    // ❌ 不能直接修改state的值
    // this.state.count++;

    // this.state.list.push(4);

    // ❌ 会导致修改原始数据都不能用
    // this.setState({ list: this.state.list });
  };

  render() {
    return (
      <div>
        <h1>{this.state.count}</h1>
        <h2>{this.state.list}</h2>
        <button onClick={this.handleAdd}>点我</button>
      </div>
    );
  }
}

2.11 React表单-受控组件

  1. 表单元素依赖于状态,表单元素需要默认值实时映射到状态的时候,就是受控组件,这个和vue中的v-model双向绑定相似
  2. 受控组件只有继承React.Component才会有状态.
  3. 实现思路:
  • state控制表单元素的value或者checked属性
  • onChange 配合setState 修改数据
  1. 受控 / 非受控组件的区别:
    • 非受控组件: 表单元素的值不受state的控制, 由dom本身管理
    • 受控组件: 不用访问DOM, 更符合数据驱动视图的思想
//受控组件演示
import React, { Component } from 'react';
export default class App extends Component {
  state = {username: 'zs',desc: '狂徒',city: '1',isSingle: true,};

//{ target: { name, type, checked, value } }---》直接在参数上将 e.target 解构
  handleinputs = ({ target: { name, type, checked, value } }) => {
    this.setState({ [name]: type === 'checkbox' ? checked : value });
  };
  render() {
    const { username, desc, city, isSingle } = this.state;
    return (
      <div>
        姓名:
        <input name="username" type="text" value={username} onChange={this.handleinputs} />
        <br />
        描述:
        <textarea name="desc" value={desc} onChange={this.handleinputs} ></textarea>
        <br />
        城市:
        <select name="city" value={city} onChange={this.handleinputs}>
          <option value="1">北京</option>
          <option value="2">上海</option>
          <option value="3">广州</option>
          <option value="4">深圳</option>
        </select>
        <br />
        是否单身:
        <input name="isSingle" type="checkbox" checked={isSingle} onChange={this.handleinputs} />
      </div>
    );
  }
}

2.12 React-ref 获取dom元素或组件实例

  1. 获取dom元素

    import React, { Component } from 'react';
    
    export default class App extends Component {
      //  1. 创建ref对象
      iptRef = React.createRef();
      handleClick = () => {
        // 3. 通过ref对象.current属性获取dom元素
        this.iptRef.current.focus();
      };
    
      render() {
        return (
          <div>
            <input
              // 2. 绑定ref给dom元素
              ref={this.iptRef}
              type="text"
            />
            <button onClick={this.handleClick}>点我看看ipt</button>
          </div>
        );
      }
    }
    
  2. 获取组件

    import React, { Component } from 'react';
    
    export default class App extends Component {
      // 1.React.createRef()调用方法创建组件
      childRef = React.createRef();
      handleClick = () => {
        // 3.this.childRef.current属性获取组件对象,可以访问组件里面的方法
        this.childRef.current.handleAlert();
      };
    
      render() {
        return (
          <div>
            <button onClick={this.handleClick}>点我获取Child组件</button>
             {/* 2.将ref绑在组件标签上 */}
            <Child ref={this.childRef} ></Child>
          </div>
        );
      }
    }
    class Child extends React.Component {
      state = {count: 100,};
      handleAlert() {alert('我是一个大baby');}
      render() {
        return <div></div>;
      }
    }
    

2.13组件通信

1. 父传子

/*子组件通过props接收数据
  本质: props对象, 所有组件标签身上的属性和值, 组成的一个对象
  推荐: 解构props对象
  语法: 
    1. 函数组件, 通过形参接收props
    2. class组件,通过this.props接收props
*/
import React, { Component } from 'react';

export default class App extends Component {
  render() {
    return (
      <div>
        {/* 组件可以传递任意的数据类型 包括 jsx 函数等 */}
        <HelloNode fn={() => alert('传递了一个函数')} testObj={{ name: '狂徒' }} msg="喵喵" count={3} ></HelloNode>
        <ChildNode title={<i>传递一段倾斜的文字</i>} msg="芒果" count={16} ></ChildNode>
      </div>
    );
  }
}
//类组件以 this.props接收数据
class HelloNode extends Component {
  render() {
    const { msg, count, testObj, fn } = this.props;
    return (
      <div>
        <h1>
          hello子组件--{msg}-{count}
        </h1>
        <hr />
        <h2>接收的对象属性值----{testObj.name}</h2>
        <button onClick={fn}>触发父组件传递的函数</button>
      </div>
    );
  }
}
//函数组件以形参对象 接收传递的数据
function ChildNode({ msg, count, title }) {
  return (
    <div>
      <h2>
        child子组件 --{msg}-{count}
      </h2>
      {title}
    </div>
  );
}

2. 子传父

/*实现子传父功能
  1. 父组件定义一个回调函数
  2. 通过props将回调函数传给子组件
  3. 子组件内通过props调用父组件传来的函数
*/
import React, { Component } from 'react';
export default class App extends Component {
  state = {money: 1000,};

  handleMakeMoney = () => {
    this.setState({
      money: 1000 + this.state.money,
    });
  };
  handleDropMoney = (num) => {
    const { money } = this.state;
    this.setState({ money: money - num });
  };

  render() {
    const { money } = this.state;
    return (
      <div>
        <h1>总共多少钱:{money}</h1>
        <button onClick={this.handleMakeMoney}>爸爸开始赚钱了</button>
        <Child dropFn={this.handleDropMoney} money={money}></Child>
      </div>
    );
  }
}

//子组件
class Child extends Component {
  render() {
    const { money, dropFn } = this.props;
    return (
      <div>
        <h1>爸爸给我钱了:{money} </h1>
            {/* 调用父组件的函数时,同时也可以向父组件传递参数 */}
        <button onClick={() => dropFn(100)}>买花花:100块</button>
        <button onClick={() => dropFn(500)}>买书籍:500块</button>
        <button onClick={() => dropFn(50)}>买零食:50块</button>
      </div>
    );
  }
}

3. 跨组件通信-状态提升

/*
  状态提升:本质上就是将子组件内的状态, 提升到共同的父组件中
  目的: 可以借助于子传父、父传子,实现兄弟组件通信
*/
import React, { Component } from 'react';

//需准备相应的子组件并按路径导入
import Husband from './components/Husband';
import Wife from './components/Wife';

export default class App extends Component {
  // 1. state提升到父组件中
  state = {money: 0,};

  // 2.定义方法
  handleMakeMoney = () => {
    this.setState({ money: this.state.money + 1000 });
  };

  // 5. 定义函数钱的函数
  handleCost = () => {
    this.setState({ money: this.state.money - 5000 });
  };

  render() {
    const { money } = this.state;
    return (
      <div>
        <h1 style={{ textAlign: 'center' }}>家庭存款:{money}</h1>
        {/* 3. 父传子传递money给子组件 */}
        <Husband handleMakeMoney={this.handleMakeMoney} money={money}></Husband>
        <hr />
        <Wife
          // 6. 通过Props传递函数给wife组件
          handleCost={this.handleCost}
        ></Wife>
      </div>
    );
  }
}

4. 跨组件通信- Context

/* 
  使用Context来跨组价通信,让App组件-直接传数据给SonSon组件
*/

import React, { Component } from 'react';

// 1. 创建Context, 解构两个组价Provider Consumer
const { Provider, Consumer } = React.createContext();

export default class App extends Component {
  render() {
    return (
      // 2. 使用Provider包住子组件
      // 3. 给Provider组件设置value属性,传数据
      <Provider value="hello React">
        <div>
          <h1>父组件</h1>
          <Son></Son>
        </div>
      </Provider>
    );
  }
}

class Son extends Component {
  render() {
    return (
      <div>
        <h2> 儿子</h2>
        <SonSon></SonSon>
      </div>
    );
  }
}

class SonSon extends Component {
  render() {
    return (
      // 4. 使用Consumer组件接收数据

      <div>
        <h2> 孙子</h2>
        <Consumer>
          {(data) => {
            return <h1>123 - {data}</h1>;
          }}
        </Consumer>
      </div>
    );
  }
}

5. props.children-组件双标签之间的内容

  1. 通过组件双标签中间区域,也可以向子组件传递数据,子组件可以通过props.children接收
  2. 双标签之间可以传递任意的数据类型,包括函数,jsx片段等
import React, { Component } from 'react';

export default class App extends Component {
  render() {
    return (
      <div>
        <Child>夹在组件标签中间的内容</Child>

        <Child>
          <i>这是一段jsx,使用了i标签</i>
        </Child>

        <Child2>{() => alert('组件标签中间也可以传递函数')}</Child2>
      </div>
    );
  }
}
function Child({ children }) {
  return <h1>child组件的-----{children}</h1>;
}
function Child2({ children }) {
  return (
    <h1>
      child2组件传递的函数,点击按钮触发
      <button onClick={children}>点我触发</button>
    </h1>
  );
}

6.props类型校验

  1. 需求场景:当组件对接收的参数有具体的类型要求时,props类型校验就显得很有必要了
/*如何给组件props添加默认值
  语法:
    1. 组件名.defaultProps = { 属性名:默认值 }
    2. 函数值组件最新方法:
      function 组件名({属性名= 默认值}){}
      💥 PropTypes包会认为没有传默认值
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class App extends Component {
  render() {
    return (
      <div>
        <Child
          name={<h1>React</h1>}
          obj={{ name: 'zs', age: 18 }}
          fn={() => {}}
          isShow={true}
          title={123}
        ></Child>
        <Child2 title={123}></Child2>
      </div>
    );
  }
}

class Child extends React.Component {
  render() {
    const { title, color } = this.props;
    return (
      <div>
        {title.toFixed(2)} - {color}
      </div>
    );
  }
}
//给参数设置默认值
Child.defaultProps = {
  color: 'red',
};

function Child2({ color = 'green' }) {
  return <h2>Child2 - {color}</h2>;
}

Child2.propTypes = {
  title: PropTypes.number,
  color: PropTypes.string.isRequired,

  isShow: PropTypes.bool,
  fn: PropTypes.func,
  obj: PropTypes.shape({
    name: PropTypes.string,
    age: PropTypes.number,
  }),
  name: PropTypes.element,
};

// 2. 给组件设置检验规则
Child.propTypes = {
  title: PropTypes.number,
  color: PropTypes.string.isRequired,

  isShow: PropTypes.bool,
  fn: PropTypes.func,
  obj: PropTypes.shape({
    name: PropTypes.string,
    age: PropTypes.number,
  }),
  name: PropTypes.element,
};

7.关于static关键词

// class成员:分为两类
// 1. 实例成员: 直接写在class中的属性和方法,而且没有static
//    特点: 只能通过实例对象.xxx去访问
// 2. 静态成员: class中有static关键字的属性和方法
//    特点: 只能通过类名.xxx去访问

// static关键字,作用:将一个实例成员,变为静态成员

import PropTypes from 'prop-types';
import React, { Component } from 'react';
export default class App extends Component {
  render() {
    return (
      <div>
        <Child title={''}></Child>
      </div>
    );
  }
}

class Child extends React.Component {
  static defaultProps = {
    color: 'red',
  };

  static propTypes = {
    title: PropTypes.number,
    color: PropTypes.string.isRequired,

    isShow: PropTypes.bool,
    fn: PropTypes.func,
    obj: PropTypes.shape({
      name: PropTypes.string,
      age: PropTypes.number,
    }),
    name: PropTypes.element,
  };

  render() {
    const { color } = this.props;
    return <div>{color}</div>;
  }
}

2.14React生命周期与钩子函数

import React, { Component } from 'react';

export default class App extends Component {
  // 不要在constructor函数中发送请求
  constructor() {
    super();
    console.log('先触发constructor');
  }
  state = {
    count: 0,
  };
  // 渲染函数
  render() {
    const { count } = this.state;
    console.log('再触发render');
    return (
      <div>
        App
        <button onClick={() => this.setState({ count: count + 1 })}>
          点击了{count}
        </button>
      </div>
    );
  }
  // 类似于 vue中的created和mounted的统合使用
  // 页面初始化发送数据请求一般放到这个钩子函数
  componentDidMount() {
    console.log('componentDidMount,组件挂载后触发');
  }
  // 当state的数据或 props的数据发生更新时,会触发该函数
  //prevProps回调参数可以捕获到更新前的props对象, prevState回调参数可以捕获到更新前的state对象
  componentDidUpdate(prevProps, prevState) {
    console.log(prevProps, prevState);
    console.log(30, this.state);
    console.log('页面初始化不触发,state的数据或 props的数据发生更新时才触发');
  }
}

2.15 setstate的几种写法

  1. setState({}) 场景:最常用,不需要连续调用setState,用对象即可最简单 特点: 连续调用,会产生覆盖效果

  2. setState((旧的state) => 新的state)

    场景:需要根据上一次的计算结果, 计算下一次的值 特点:连续调用,会产生串联等待上次计算结果的效果

  3. setState({}, () => {})

    场景: 需要根据上一次的计算结果, 计算下一次的值 特点: 不会产生合并效果, 容易浪费性能。 2. 容易回调地域

写法一:setState(更新的state),**

特点:这种写法连续调用setState, 最后一次setState会覆盖前面的setState

//目的: React为了减少操作dom的次数, 节省性能
import React, { Component } from 'react';
export default class App extends Component {
  state = {
    count: 0,
  };

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 2 });
    this.setState({ count: this.state.count + 3 });//不执行+1+2,只执行最后一次的+3
    console.log('this.state.count  ----->', this.state.count);//打印的是 0,更新前的值
  };
  render() {
    const { count } = this.state;
    console.log('render被触发了  ----->  ');
    return (
      <div>
        <h1>{count}</h1>
        <button onClick={this.handleClick}>点我+1</button>
      </div>
    );
  }
}

写法二: setState((旧的state) => 更新的state )

特点:这种写法下连续调用setstate,前一个setstate里返回的数据会依次传递给下一个setstate的函数形参prestate,会在最后一次调用时将多次计算后的值赋给state

import React, { Component } from 'react';

export default class App extends Component {
  state = {
    count: 0,
  };

  handleClick = () => {
    this.setState((preState) => {
      console.log(this.state.count);//0
      console.log(preState.count);//0
      return {
        count: preState.count + 1,
      };
    });

    this.setState((preState) => {
      console.log(this.state.count);//0
      console.log(preState.count);//1
      return {
        count: preState.count + 2,
      };
    });

    this.setState((preState) => {
      console.log(this.state.count);//0
      console.log(preState.count);//3
      return {
        count: preState.count + 3,
      };
    });
  };
  render() {
    const { count } = this.state;
    console.log('render执行几次  ----->  ');//只打印一次
    return (
      <div>
        <h1>{count}</h1>
        <button onClick={this.handleClick}>点我+1</button>
      </div>
    );
  }
}

写法三: setState(更新的state , () => {}) ,参数二的函数会在值更新后触发

特点:如果在setstate的回调函数中再次调用setstate, 不会形成合并, 不会减少dom更新次数,会导致回调地域问题,消耗性能(不推荐)。

import React, { Component } from 'react';
export default class App extends Component {
  state = {
    count: 0,
  };

  handleClick = () => {
    this.setState({ count: this.state.count + 1 }, () => {
      this.setState({ count: this.state.count + 2 }, () => {
        this.setState({ count: this.state.count + 3 });
      });
    });
  };
  render() {
    const { count } = this.state;
    console.log('render执行几次  ----->  ');//会执行三次
    return (
      <div>
        <h1>{count}</h1>
        <button onClick={this.handleClick}>点我+1</button>
      </div>
    );
  }
}

2.16React的更新机制-组件

/*默认情况下: 
    1. 父组件更新, 所有后代全部更新
    2. 组件更新, 不会导致兄弟组件更新
*/
import React, { Component } from 'react';
export default class App extends Component {
  state = {
    appCount: 0,
  };
  handleAppAdd = () => {
    this.setState({ appCount: this.state.appCount + 1 });
  };
  render() {
    return (
      <div>
        <h1>{this.state.appCount}</h1>
        {/* 父组件状态更新时,所有子组件的render都会触发 */}
        <button onClick={this.handleAppAdd}>点击更新父组件的state</button>
        <Child></Child>
        <Child2></Child2>
      </div>
    );
  }
}

class Child extends React.Component {
  state = {
    count: 100,
  };

  handleAdd = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    console.log('Child被触发更新了  ----->  ');
    return (
      <div>
        Child
        {/* 子组件的状态更新时,只触发自身的render,兄弟组件不会触发render, */}
        <button onClick={this.handleAdd}>点我+1</button>
      </div>
    );
  }
}

class Child2 extends React.Component {
  render() {
    console.log('Child2被触发更新了  ----->  ');
    return <div>Child2</div>;
  }
}

使用shouldComponentUpdate() 进行更新优化

import React, { Component } from 'react';

export default class App extends Component {
  state = {
    count: 100,
    msg: 'hello React',
  };

  handleAdd = () => {
    this.setState({ count: this.state.count + 1 });
  };

  handleAddMsg = () => {
    this.setState({ msg: this.state.msg + '~' });
  };

  render() {
    const { count, msg } = this.state;
    return (
      <div>
        <h1>{count}</h1>
        <h1>{msg}</h1>
        <button onClick={this.handleAdd}>点我+1</button>
        <button onClick={this.handleAddMsg}>点我+~</button>
        <Child count={count}></Child>
      </div>
    );
  }
}

class Child extends React.Component {
//nextProps形参可以拿到新的state,可以利用新旧值得对比,决定返回 true 或 false
//当shouldComponentUpdate函数返回一个 false 组件就不会再更新 为true则会继续更新
  shouldComponentUpdate(nextProps) {
    if (nextProps.count !== this.props.count) {
      return true;
    }
    return false;
  }

  render() {
    console.log('Child被更新了  ----->  ');
    return <div>Child - {this.props.count}</div>;
  }
}

使用React.PureComponent 纯组件进行更新优化(不要滥用)

import React, { Component } from 'react';
export default class App extends Component {
  state = {
    count: 0,
    msg: 'hello',
  };
  render() {
    return (
      <div>
        App
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          点我+1
        </button>
        <button onClick={() => this.setState({ msg: this.state.msg + '~' })}>
          点我文字加~
        </button>
        <Child count={this.state.count}></Child>
      </div>
    );
  }
}
// React.PureComponent 纯组件,会将preprops和nextprops进行浅比较,只比较数据的引用地址,不会比较值
// 这就是为什么react一再强条的状态不可变,要使用新值覆盖旧值
class Child extends React.PureComponent {
  render() {
    console.log('触发子组件render');
    return <div>child1</div>;
  }
}

3.路由

3.1js模拟路由

import React from 'react';
export default class App extends React.Component {
  state = {
    currentHash: '/home',
  };
  componentDidMount() {
    // 挂载后获取url赋值一次
    this.setState({ currentHash: window.location.hash.slice(1) });
    window.addEventListener('hashchange', () => {
      // 监听hashchange事件,并更新状态
      console.log(window.location);
      this.setState({ currentHash: window.location.hash.slice(1) });
    });
  }
  render() {
    const { currentHash } = this.state;
    return (
      <div>
        <h1>app组件</h1>
        <ul>
          <li>
            <a href="#/home">首页</a>
          </li>
          <li>
            <a href="#/my">我的音乐</a>
          </li>
          <li>
            <a href="#/friend">我的朋友</a>
          </li>
        </ul>
        {/* 根据状态对组件条件渲染 */}
        {currentHash === '/home' && <Home></Home>}
        {currentHash === '/my' && <MyMusic></MyMusic>}
        {currentHash === '/friend' && <Friend></Friend>}
      </div>
    );
  }
}
function Home() {
  return <h1>我是首页组件</h1>;
}

function MyMusic() {
  return <h1>我是我的音乐件</h1>;
}

function Friend() {
  return <h1>我是朋友组件</h1>;
}

3.2React路由的基本使用

/*步骤:
     1. 下包 react-router-dom@5.3
     2. 导入三个组件HashRouter Route Link
     	HashRouter 
          作用: 实例化路由, 💥全局只调用一次
          类似: Vue中的new VueRouter
          注意: 包住全部的代码
        Route组件
          作用: 设置路由规则, 作为挂载点
          类似: vue中的规则对象 和 挂载点  二合一
        Link组价:
          作用:跳转路由 to="/路径"
          本质: a标签  router-link
*/
import React, { Component } from 'react';
import { HashRouter, Route, Link } from 'react-router-dom';
export default class App extends Component {
  render() {
    // 3. 使用HashRouter包住所有的代码 -
    return (
      <HashRouter>
        {/* 5. 通过Link组件去跳转 */}
        <Link to="/home">点我调到首页</Link>
        <br />
        <Link to="/my">点我调到我的</Link>
        <br />
        <Link to="/friend">点我调到朋友</Link>
        <div>
          {/* 4. 设置路由规则: Route组件设置
           */}
          <div className="my_home">
            <Route path="/home" component={Home}></Route>
          </div>
          <Route path="/my" component={MyMusic}></Route>
          <Route path="/friend" component={Friend}></Route>
        </div>
      </HashRouter>
    );
  }
}

function Home() {
  return <h1>我是首页组件</h1>;
}

function MyMusic() {
  return <h1>我是我的音乐件</h1>;
}

function Friend() {
  return <h1>我是朋友组件</h1>;
}

3.3路由模式切换

import React, { Component } from 'react';

// HashRouter 哈希路由模式
// BrowserRouter 历史路由模式
// 推荐通过as 将路由重命名为Router ,方便后期修改路由模式
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
export default class App extends Component {
  render() {
    return (
      <Router>
        <Link to="/home">点我跳到首页</Link>
        <br />
        <Link to="/my">点我跳到我的</Link>
        <br />
        <Link to="/friend">点我跳到朋友</Link>
        <div>
          <div className="my_home">
            <Route path="/home" component={Home}></Route>
          </div>
          <Route path="/my" component={MyMusic}></Route>
          <Route path="/friend" component={Friend}></Route>
        </div>
      </Router>
    );
  }
}

function Home() {
  return <h1>我是首页组件</h1>;
}

function MyMusic() {
  return <h1>我是我的音乐件</h1>;
}

function Friend() {
  return <h1>我是朋友组件</h1>;
}

3.4NavLink的使用

  1. NavLink标签与Link标签的区别在于,可以方便设置点击样式
  2. NavLink链接点击时,会添加一个'active'类名,可通过设置active类的样式来定义被点击的NavLink标签样式
  3. 当需要设置自定义类名作为点击样式时,可通过NavLink标签的activeClassName属性设置自定义active类名
import React, { Component } from 'react';
import {
  BrowserRouter as Router,
  Route,
  // Link,
  NavLink,//导入NavLink组件
} from 'react-router-dom';
import './apptest.css';//导入自定义样式表
export default class App extends Component {
  render() {
    return (
      <Router>
        {/* <Link to="/home">跳转到首页</Link> */}
        <NavLink to="/home" activeClassName="testactive">
          跳转到首页
        </NavLink>
        <br />
        {/* <Link to="/my">我的音乐</Link> */}
        <NavLink to="/my" activeClassName="testactive">
          我的音乐
        </NavLink>
        <br />
        {/* <Link to="/friend">跳转到朋友</Link> */}
        <NavLink to="/friend" activeClassName="testactive">
          跳转到朋友
        </NavLink>
        <br />
        <Route path="/home" component={Home}></Route>
        <Route path="/my" component={MyMusic}></Route>
        <Route path="/friend" component={Friend}></Route>
      </Router>
    );
  }
}

function Home() {
  return <h1>我是首页组件</h1>;
}

function MyMusic() {
  return <h1>我是我的音乐件</h1>;
}

function Friend() {
  return <h1>我是朋友组件</h1>;
}

3.5Route组件的说明

  1. Route 组件的作用相当于 路由规则和挂载点的集合
  2. Route 组件默认模糊匹配,只要url包含 (to 或path) 的值,对应组件就会显示
  3. Route组件添加 exact 表示精准匹配 url严格等于 to或path;
import React, { Component } from 'react';
import {
  BrowserRouter as Router,
  Route,
  NavLink,
} from 'react-router-dom';
import './apptest.css';
export default class App extends Component {
  render() {
    return (
      <Router>
        {/* 添加 exact 表示精准匹配  url严格等于 to或path;
        模糊匹配模式下,只要url包含 to 或path 对应组件就会显示*/}
        <NavLink to="/home" activeClassName="testactive">
          跳转到首页
        </NavLink>
        <br />
        <NavLink to="/my" activeClassName="testactive">
          我的音乐
        </NavLink>
        <br />
        <NavLink to="/friend" activeClassName="testactive">
          跳转到朋友
        </NavLink>
        <br />
        {/* Route 组件的作用相当于 路由规则和挂载点的集合 */}
        {/* Route组件添加 exact 表示精准匹配  url严格等于 to或path;
        模糊匹配模式下,只要url包含 to 或path 对应组件就会显示*/}
        {/* 模糊匹配下 url='/home/1234' 此时path='/home'的组件也会显示 */}
        <Route path="/home" component={Home}></Route>
        <Route path="/my" component={MyMusic} exact></Route>
        <Route path="/friend" component={Friend}></Route>
      </Router>
    );
  }
}

function Home() {
  return <h1>我是首页组件</h1>;
}

function MyMusic() {
  return <h1>我是我的音乐件</h1>;
}

function Friend() {
  return <h1>我是朋友组件</h1>;
}

3.6 Switch组件

  1. Switch组件的特点: 匹配到任意一个,即停止向下匹配
import React, { Component } from 'react';
import './index.css';
// 👍通过as 将路由重命名为Router
import { BrowserRouter as Router, Route, Link, NavLink, Switch } from 'react-router-dom';
export default class App extends Component {
  render() {
    return (
      <Router>
        <NavLink to="/home/123" activeClassName="xxx" exact>
          点我调到首页
        </NavLink>
        <br />
        <NavLink to="/my">点我调到我的</NavLink>
        <br />
        <NavLink to="/friend">点我调到朋友</NavLink>
        <div>
          {/* Switch组件的特点: 匹配到任意一个,即停止向下匹配 */}
          {/* 👍 使用Route组件,直接包在Switch组件中 */}
          <Switch>
            {/* Switch组件包裹时,即使有三个home组件的挂载点,也只会在匹配到第一个后就不往后匹配 */}
            <Route path="/home" component={Home}></Route>
            <Route path="/home" component={Home}></Route>
            <Route path="/home" component={Home}></Route>
            <Route path="/my" component={MyMusic}></Route>
            <Route path="/friend" component={Friend}></Route>
            {/* 👍不写path 通常放在Switch组件的最后一个,表示兜底,匹配任意路径 */}
            {/* 场景:通常用来做404页面 */}
            <Route component={NotFound}></Route>
          </Switch>
        </div>
      </Router>
    );
  }
}

function NotFound() {
  return <div>404页面</div>;
}

function Home() {
  return <h1>我是首页组件</h1>;
}

function MyMusic() {
  return <h1>我是我的音乐件</h1>;
}

function Friend() {
  return <h1>我是朋友组件</h1>;
}

3.7 路由嵌套

  1. 二级路由的路由配置等定义在一级路由组件中
import React, { Component } from 'react';
import './index.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch } from 'react-router-dom';
export default class App extends Component {
  render() {
    return (
      <Router>
        <NavLink to="/home/123" activeClassName="xxx" exact>
          点我调到首页
        </NavLink>
        <br />
        <NavLink to="/my">点我调到我的</NavLink>
        <br />
        <NavLink to="/friend">点我调到朋友</NavLink>
        <div>
          <Switch>
            <Route path="/home" component={Home}></Route>
            <Route path="/my" component={MyMusic}></Route>
              
              {/*嵌套路由中父组件一般不能使用exact,会导致匹配不到页面组件 */}
            <Route path="/friend" component={Friend}></Route>

            <Route component={NotFound}></Route>
          </Switch>
        </div>
      </Router>
    );
  }
}

function Friend() {
  return (
    <div>
      <h1>我是朋友组件</h1>
      {/* 在父组件中再使用一次Switch + Route */}
      {/* 嵌套路由中: 父子级路径可以同名。 两个组件都会显示 */}
      {/* 💥 注意:1. 嵌套路由中不需要在使用Router组件 */}
      {/* 💥 注意:2. 嵌套路由中父组件一般不能使用exact */}
      {/* 💥 注意:3. 跳转路由的Link组件,要从/一级路径开始写 */}
      <Link to="/friend/friend1">调到朋友1</Link>
      <Link to="/friend/friend2">调到朋友2</Link>
      <Link to="/friend/friend3">调到朋友3</Link>

      <Switch>
        <Route path="/friend/friend1" component={Friend1}></Route>
        <Route path="/friend/friend2" component={Friend2}></Route>
        <Route path="/friend/friend3" component={Friend3}></Route>
      </Switch>
    </div>
  );
}

function NotFound() {
  return <div>404页面</div>;
}

function Home() {
  return <h1>我是首页组件</h1>;
}

function MyMusic() {
  return <h1>我是我的音乐件</h1>;
}

function Friend1() {
  return <i>我是朋友子组件1</i>;
}
function Friend2() {
  return <i>我是朋友子组件2</i>;
}
function Friend3() {
  return <i>我是朋友子组件3</i>;
}

3.8 Redirect重定向组件

  1. 应用场景:重定向到首页 / 重定向到登录界面
  2. Redirect 通常配合exact使用
  3. path属性与from属性一致表示从哪个页面来,to属性定义重定向的页面
import React, { Component } from 'react';
import './index.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';
export default class App extends Component {
  render() {
    return (
      <Router>
        <NavLink to="/home/123" activeClassName="xxx" exact>
          点我调到首页
        </NavLink>
        <br />
        <NavLink to="/my">点我调到我的</NavLink>
        <br />
        <NavLink to="/friend">点我调到朋友</NavLink>
        <div>
          <Switch>
            {/* 💥 场景: 1. 重定向到首页  2. 重定向到登录界面 */}
            {/* 💥 Redirect 通常配合exact使用 */}
            <Redirect path="/" to="/home" exact></Redirect>
            <Route path="/home" component={Home}></Route>
            <Route path="/my" component={MyMusic}></Route>
            <Route path="/friend" component={Friend}></Route>
            <Route component={NotFound}></Route>
          </Switch>
        </div>
      </Router>
    );
  }
}

function Friend() {
  return (
    <div>
      <h1>我是朋友组件</h1>
    </div>
  );
}

function NotFound() {
  return <div>404页面</div>;
}

function Home() {
  return <h1>我是首页组件</h1>;
}

function MyMusic() {
  return <h1>我是我的音乐件</h1>;
}

3.9动态路由-路由传参

  1. 改造Route组件的path属性 语法:path="/路径/:自定义属性名
  2. 页面组件中通过props.match.params.自定义属性名, 获取动态路由的参数值

import React, { Component } from 'react';
import './index.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';
export default class App extends Component {
  render() {
    return (
      <Router>
        <NavLink to="/my">点我调到我的</NavLink>

        <div>
          <Switch>
            {/* 1. 改造路由的path属性 语法:path="/路径/:自定义属性名" */}
            <Route path="/my/:id" component={MyMusic}></Route>
          </Switch>
          <Header></Header>
        </div>
      </Router>
    );
  }
}

function MyMusic({ match }) {
  // 2. 通过props.match.params.自定义属性名, 获取动态路由的参数值
  console.log('params  ----->  ', match.params.id);
  return <h1>我是我的音乐件 </h1>;
}

3.10编程式导航-路由跳转

  1. 实现原理:Route组件设置过匹配规则的组件有history对象,该对象包含push("/路径") / go(数字) / goBack()回退等路由跳转方法
  2. 注意:只有Route组件设置过匹配规则的组件才有history对象,普通组件没有history
/*编程式导航-通过JS跳转路由
  语法: props.history操作路由跳转
  常用: 1. push("/路径")  2. go(数字) 3. goBack()回退
  注意: 只有Route组件设置过匹配规则的组件才有history对象,普通组件没有history
*/
import React, { Component } from 'react';
import './index.css';
import { BrowserRouter as Router, Route, Link, NavLink, Switch, Redirect } from 'react-router-dom';
export default class App extends Component {
  render() {
    return (
      <Router>
        <NavLink to="/home/123" activeClassName="xxx" exact>
          点我调到首页
        </NavLink>
        <br />
        <NavLink to="/my">点我调到我的</NavLink>
        <br />
        <NavLink to="/friend">点我调到朋友</NavLink>
        <div>
          <Switch>
            <Redirect path="/" to="/home" exact></Redirect>
            <Route path="/home" component={Home}></Route>
            <Route path="/my" component={MyMusic}></Route>
            <Route path="/friend" component={Friend}></Route>
            <Route component={NotFound}></Route>
          </Switch>
          <Header></Header>
        </div>
      </Router>
    );
  }
}

function Friend() {
  return (
    <div>
      <h1>我是朋友组件</h1>
      <Link to="/friend">调到朋友1</Link>
      <Link to="/friend/friend2">调到朋友2</Link>
      <Link to="/friend/friend3">调到朋友3</Link>
      <Switch>
        <Route path="/friend" component={Friend1} exact></Route>
        <Route path="/friend/friend2" component={Friend2}></Route>
        <Route path="/friend/friend3" component={Friend3}></Route>
      </Switch>
    </div>
  );
}

function Header(params) {
  return <h1>Header</h1>;
}

function NotFound() {
  return <div>404页面</div>;
}

class Home extends React.Component {
  handleClick = () => {
    this.props.history.push('/my');
    //this.props.history.go(1);
  };

  render() {
    return (
      <div>
        <h1>我是首页组件</h1>
        <button onClick={this.handleClick}>点我跳转</button>
      </div>
    );
  }
}

function MyMusic({ history }) {
  return (
    <h1>
      我是我的音乐件{' '}
      <button
        onClick={() => {
          // history.go(-1);
          history.goBack();
        }}
      >
        点我后退
      </button>
    </h1>
  );
}

function Friend1() {
  return <i>我是朋友组件1</i>;
}
function Friend2() {
  return <i>我是朋友组件2</i>;
}
function Friend3() {
  return <i>我是朋友组件3</i>;
}