react(api)

1.react是什么

用于构建用户界面的JavaScript库,将数据渲染为HTML视图的开源javaScript库

2.为什么要学react

1.原生的JavaScript操作DOM繁琐,效率低。

2.使用原生的JavaScript直接操作DOM,浏览器会进行大量的重新绘制,重新排序。

3.原生JavaScript没有组件化编码方案,代码复用率低。

3.React的特点

1.采用组件化模式、声明式编码,提高开发效率及组件复用率。

2.在React Native中可以使用React语法进行移动端开发。

3.使用虚拟DOM和优秀的Diffing算法,尽量减少与真实DOM的交互。

4.学之前要准备好四个js包

 1.babel.js(1.用于ES6转ES5。2.对引入语句import浏览器识别不了进行转换。3.jsx转js)

 2.react.development.js (react核心库)

 3.react-dom.development.js (react扩展库,用于react帮你操作dom)

 4.prop-types.js (react的传参类型库)

5.入门React

1.jsx创建虚拟DOM

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
    <!-- 1.准备一个容器 -->
    <div id="root"></div>
    <!-- 2.引入react核心库 -->
    <script src="../依赖包/旧版本/react.development.js"></script>
    <!-- 3.引入react-dom,用于支持react操作dom -->
    <script src="../依赖包/旧版本/react-dom.development.js"></script>
    <!-- 4.引入babel,用于将jsx转为js -->
    <script src="../依赖包/旧版本/babel.min.js"></script>  
     <!-- 5.默认是text/javascript,但现在写的不是js,是jsx所以要用babel -->
    <script type="text/babel">
    // 因为react是操作虚拟dom所以第一步
    // 1.创建一个虚拟dom
    const VDOM=<h1>Hello,React</h1>
    //2.渲染虚拟dom到页面
    ReactDOM.render(VDOM,document.getElementById('root'))
    </script>
</body>
</html>

2.js创建虚拟dom

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>js创建虚拟DOM</title>
</head>
<body>
    <!-- 1.准备一个容器 -->
    <div id="root"></div>
    <!-- 2.引入react核心库 -->
    <script src="../依赖包/旧版本/react.development.js"></script>
    <!-- 3.引入react-dom,用于支持react操作dom -->
    <script src="../依赖包/旧版本/react-dom.development.js"></script>
    <script type="text/javascript">
    // 因为react是操作虚拟dom所以第一步
    // 1.创建一个虚拟dom
    //React.createElement()创建虚拟dom,三个参数 1.标签名 2.标签属性 3.标签内容
    const VDOM=React.createElement('h1',{id:'title'},'hellow React')
    //2.渲染虚拟dom到页面
    ReactDOM.render(VDOM,document.getElementById('root'))
    </script>
</body>
</html>

为啥要用jsx不用js?

因为js创建多个层级嵌套的dom结构太繁琐

如果想在h1里加个span标签需要在内容体里在来一遍React.createElement,而jsx直接在创建虚拟DOM的h1里面写一下就行了

jsx

 js

 jsx语法是js语法的语法糖,jsx执行后就会变成js这种,但是为了我们不用直接写

什么是虚拟DOM?

1.他是一个object类型的对象(一般对象)

2.虚拟DOM比较"轻",真实DOM比较"重",因为虚拟DOM是React内部在用,无需真实DOM上那么多属性。

3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

什么是jsx?

1.全称是JavaScript XML。

2.react定义的一中类似于XML的js扩展语法:js+XML。

3.本质是React.createElement()方法的语法糖。

4.用来简化创建虚拟DOM。

jsx语法规则

1.定义虚拟dom时不要加引号

2.标签中混入js表达式时要加{}。

3.样式的指定类名不能用class,要用className。(小知识:因为jsx要区分ES6的类class,所以用className)

4.行类样式要采用style={{key:value}}的形式去写。(fontSize:"20px",过长的要用驼峰)

 5.虚拟DOM必须只有一个根标签。

6.标签必须闭合。

7.标签首字母。

   (1)若小写字母开头,则会转化为html的同名元素,若html中没该标签同名元素,则报错。

   (2)若大写开头,react就去渲染对应的组件,若没有定义则报错。

JSX小练习(循环)

 

 这里为什么用map,因为{}里接受的是js表达式

表达式:就是你拿这个变量接,有返回值的

 组件

函数式(简单)组件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
    <!-- 1.准备一个容器 -->
    <div id="root"></div>
    <script src="../依赖包/旧版本/react.development.js"></script>
    <script src="../依赖包/旧版本/react-dom.development.js"></script>
    <script src="../依赖包/旧版本/babel.min.js"></script>  
    <script type="text/babel">
      function MyComponent(){
        console.log(this)
        return (
          <div>
            欢迎来到函数式组件
          </div>
        )
      }

    //2.渲染虚拟dom到页面
    ReactDOM.render(<MyComponent />,document.getElementById('root'))
    </script>
</body>
</html>

关于this

一般来说定义的函数在window里调用this指的是window,但此处的this是undefined,因为babel编译后开启了严格模式,禁止this指向window。

执行了ReactDOM.render(<MyComponent />,document.getElementById('root'))之后发生了什么?

1.React解析组件标签,找到MyComponent组件

2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转化为真是DOM,随后呈现在页面中。

关于类

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    //创建一个Person类
    class Person {
      //构造器方法
      constructor(name, age) {
        //构造器的this是new的实例对象,p1 new的就是p1
        this.name = name
        this.age = age

      }

      speak() {
        //speak方法放在哪里?放在了调用者原型的上,供调用者使用
        //通过Person实例调用speak时,speak中的this就是Person实例,不能说speak中的this就是Person实例因为通过apply,bind,call调用是可以更改this指向的
        console.log(`他的名字是${this.name},他今年${this.age}`);
      }
    }
    //创建一个Person的实例对象
    const p1 = new Person('哪吒', 15)
    const p2 = new Person('申公豹', 30)
    p1.speak()
    console.log(p1.__proto__.speak); //在原型上看speak方法
    p2.speak.call({ name: '小明', age: 30 })
    console.log(p2);
    //创建一个Student类继承Person
    class Student extends Person {
      constructor(name, age, skill) {
        super(name, age)//当父类有共同属性时必须要加super(super帮你调用父类的构造器)
        this.skill = skill
      }
    }
    const s1 = new Student('小猪熊', 4, '唱歌')
    console.log(s1, 9);
    //总结
    //类里面的构造器不是一定要写的,要对进行一些初始化操作,如添加指定属性时才写
    //如果A类继承了B类,且A类里有构造器constructor,那么A类构造器里一定要写super来调用
    //类里所定义的方法都是放在类的原型对象上,供调用者使用
  </script>
</body>

</html>

类式组件(复杂组件)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>类式组件</title>
</head>
<body>
    <!-- 1.准备一个容器 -->
    <div id="root"></div>
    <script src="../依赖包/旧版本/react.development.js"></script>
    <script src="../依赖包/旧版本/react-dom.development.js"></script>
    <script src="../依赖包/旧版本/babel.min.js"></script>  
    <script type="text/babel">
      //创建类式组件
      class MyComponent extends React.Component{
        //render 这里的render和外面的render是不一样的,这里的render放在类(MyComponent)的原型对象上,供实例使用
        render(){
          return(
            <div>
            欢迎来到类式组件
            </div>
          )
        }
      }

    //2.渲染虚拟dom到页面
    ReactDOM.render(<MyComponent />,document.getElementById('root'))
    </script>
</body>
</html>

render里的this是,类(MyComponent)的实例对象

打印this,类里的方法都源于继承的React.Component        

既然说rander的方法在实例上,但没有调用new调用这个实例我们把目光放在React.render上

执行了ReactDOM.render(<MyComponent />,document.getElementById('root'))之后发生了什么?

1.React解析组件标签,找到MyComponent组件

2.发现组件是使用类定义的,随后new出该类的实例,并通过实例调用原型上的render方法,将render返回的虚拟DOM转化为真实DOM,呈现在页面上。

对以上简单和复杂组件解释

简单组件没有state,复杂组件有state,(当然后期函数式组件引进useState)

这个state的是组件的实例对象上的,函数组件里连自己的this都没有所以接下来的组件实例的三大属性就去除了函数式组件。

组件的三大属性

1.state及事件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>类式组件</title>
</head>

<body>
  <div id="root"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script type="text/babel">
    //创建类式组件
    class MyComponent extends React.Component {
      //初始化状态
      constructor(props) {
        super(props)
        this.state = {
          weather: true
        }
      }
      render() {
        const { weather } = this.state
        return (
          <div>
            <button onClick={btn}>点击切换天气</button>
            <span>今天天气很{weather ? '凉爽' : '炎热'}。</span>
          </div>
        )
      }
    }
    function btn() {
      console.log(111);
    }
    //2.渲染虚拟dom到页面
    ReactDOM.render(<MyComponent />, document.getElementById('root'))
  </script>
</body>

</html>

这里button上的onClick绑定的事件为什么要加花括号?

因为是jsx语法不加括号是字符串

这里button上的onClick绑定的事件后面为什么不像原生那样btn加()?

因为react在渲染组件的时候,帮你new了一个MyComponent实例,通过实例调用到里面的render方法想得到返回值就要执行return里面的代码,然后你加了括号,就是函数调用的方式,把demo这个函数的返回值交给onClick,第一次执行是因为函数的调用然后react就直接执行了btn这个方法,你还没点就打印了,后面点击没用是因为demo的返回值是undefined,去掉括号就是把demo这个函数交给onClick作为回调,等到你点击帮你调dome实现点击事件。

但上面那么写太繁琐且写在类的外面

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>类式组件</title>
</head>

<body>
  <div id="root"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script type="text/babel">
    //创建类式组件
    class MyComponent extends React.Component {
      //初始化状态
      constructor(props) {
        super(props)
        this.state = {
          weather: true
        }
      }
      render() {
        const { weather } = this.state
        return (
          <div>
            <button onClick={this.btn}>点击切换天气</button>
            <span>今天天气很{weather ? '凉爽' : '炎热'}。</span>
          </div>
        )
      }
       btn() {
        console.log(this); 
      }
    }
    //2.渲染虚拟dom到页面
    ReactDOM.render(<MyComponent />, document.getElementById('root'))
  </script>
</body>

</html>

为什么这一块button上面用this.btn

因为btn这个方法在实例MyComponent的原型上,不加this是直接调用找不到。

听到这有点困惑,为什么render在原型上里面的this是这个类的实例,但btn里的this不行?

因为render是在创建组件时,类的实例直接调用的,所以有this

为什么这里打印this为undefined

因为类里所定义的方法,在内部默认开启了严格模式('use strict'),再加上btn这个函数不是通过实例调用的而是作为onClick的回调,直接调用的,这也就导致this为undefined。

解决:this为undefined

 这是一个赋值语句,首先构造器的this是类的实例对象,先从自身(实例对象)找btn这个函数,自身(实例对象)没有再从原型上找到this.btn这个方法把他等于this.btn.bind(this),bind:做了两件事,1.创建了一个新的函数,2.更改这个函数的this指向,这么一来构造器里的this是类的实例对象,然后就创建了一个新的btn,并把这个btn放在实例对象上,这时候你访问this.btn读取的是实例对象上的btn,既然是实例对象上的btn,那么就有实例的this了。

接下来切换天气

更新动态要用this.setState,且是一种合并

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>类式组件</title>
</head>

<body>
  <div id="root"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script type="text/babel">
    //创建类式组件
    class MyComponent extends React.Component {
      //初始化状态
      constructor(props) {
        super(props)
        this.state = {
          weather: true
        }
        this.btn = this.btn.bind(this)
      }
      render() {
        const { weather } = this.state
        return (
          <div>
            <button onClick={this.btn}>点击切换天气</button>
            <span>今天天气很{weather ? '凉爽' : '炎热'}。</span>
          </div>
        )
      }
      btn() {
        let weather = this.state.weather
        this.setState({ weather: !weather })
      }
    }
    //2.渲染虚拟dom到页面
    ReactDOM.render(<MyComponent />, document.getElementById('root'))
  </script>
</body>

</html>

现在问题构造器执行了几次  答案1

render执行了 1+n次

执行顺序先构造器,构造器调完了实例出来,实例出来了才能调render

然后setState执行完后,会去更新render达到数据在视图上更新。

简写方式(省略构造器)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>类式组件</title>
</head>

<body>
  <div id="root"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script type="text/babel">
    //创建类式组件
    class MyComponent extends React.Component {
      state = {
        weather: true,
      }
      render() {
        const { weather, num } = this.state
        return (
          <div>
            <button onClick={this.btn}>点击切换天气</button>
            <span>今天天气很{weather ? '凉爽' : '炎热'}。</span>
          </div>
        )
      }
      btn = () => {
        let weathers = this.state.weather
        this.setState({ weather: !weathers })
      }
    }
    //2.渲染虚拟dom到页面
    ReactDOM.render(<MyComponent />, document.getElementById('root'))
  </script>
</body>

</html>

2.props (用于接收组件传过来的值)

传参简写

给props添加接参类型(写在类的外面,用实例加)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>props的参数限制</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <!-- 引入类型包 -->
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class MyComponent extends React.Component {
      state = {
        weather: '晴天',
      }
      render() {
        const { weather } = this.state
        const { name, age, sex,speak } = this.props
        return (
          <div>
            <ul>
              <li>{weather}</li>
              <li>姓名:{name}</li>
              <li>年龄:{age + 1}</li>
              <li onClick={speak}>性别:{sex}</li>
            </ul>
          </div>
        )
      }
    }
    //给MyComponent的prop增加类型和必填
    MyComponent.propTypes = {
      //类型为string,必传
      name:PropTypes.string.isRequired,
      sex:PropTypes.string,
      age:PropTypes.number.isRequired,
      //传方法
      speak:PropTypes.func.isRequired,
    }
    //给MyComponent的prop添加默认值,如果不传性别是男
    MyComponent.defaultProps={
      sex:'男'
    }
    function speak(){
      console.log('说话');
    }
    const p = { name: '李华', age: 20, speak:function(){ console.log('唱歌');} }
    ReactDOM.render(<MyComponent name='小明' age={18} speak={speak} />, document.getElementById('root'))
    ReactDOM.render(<MyComponent {...p} />, document.getElementById('root2'))
  </script>
</body>

</html>

常用:给props加类型默认值限制(在类里写)

static 加上他之后你在类里定义的属性都不会在类的实例上,而是在他这个类本身加属性。

 那上面的类型MyComponent.propTypes就能简写成以下的

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>props的参数限制</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <!-- 引入类型包 -->
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class MyComponent extends React.Component {
      state = {
        weather: '晴天',
      }
      render() {
        const { weather } = this.state
        const { name, age, sex, speak } = this.props
        return (
          <div>
            <ul>
              <li>{weather}</li>
              <li>姓名:{name}</li>
              <li>年龄:{age + 1}</li>
              <li onClick={speak}>性别:{sex}</li>
            </ul>
          </div>
        )
      }
      //给MyComponent的prop增加类型和必填
      static propTypes = {
        //类型为string,必传
        name: PropTypes.string.isRequired,
        sex: PropTypes.string,
        age: PropTypes.number.isRequired,
        //传方法
        speak: PropTypes.func.isRequired,
      }
      //给MyComponent的prop添加默认值,如果不传性别是男
      static defaultProps = {
        sex: '男'
      }
    }
    function speak() {
      console.log('说话');
    }
    const p = { name: '李华', age: 20, speak: function () { console.log('唱歌'); } }
    ReactDOM.render(<MyComponent name='小明' age={18} speak={speak} />, document.getElementById('root'))
    ReactDOM.render(<MyComponent {...p} />, document.getElementById('root2'))
  </script>
</body>

</html>

以上是不写构造器,要是写上构造器,构造器里和super里一定要接props,不然就不能通过this.props拿到值。以下打印undefined

函数式组件使用props 

3.refs(用来获取dom节点,类似与vue ref)

1.字符串类型的ref(即将废弃,不推荐)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>字符串ref</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class Demo extends React.Component {
      btn=()=>{
        const {input1}=this.refs
        alert(`${input1.value}`)
      }
      btn2=()=>{
        const {input2}=this.refs
        alert(`${input2.value}`)
      }
      render() {
        return (
          <div>
            <input type="text"  ref='input1'/> 
            <button onClick={this.btn}>点击获取左边的input值</button>
            <input type="text"  onBlur={this.btn2}  ref='input2'/> 
          </div>
        )
      }
    }
    ReactDOM.render(<Demo/>, document.getElementById('root'))
  </script>
</body>

</html>

2.回调形式的ref(在组件实例自身上放input1和input2)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>字符串ref</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class Demo extends React.Component {
      btn=()=>{
        const {input1}=this
        alert(`${input1.value}`)
      }
      btn2=()=>{
        const {input2}=this
        alert(`${input2.value}`)
      }
      render() {
        return (
          <div>
            <input type="text"  ref={(c)=>{this.input=c}}/> 
            <button onClick={this.btn}>点击获取左边的input值</button>
             简写形式
            <input type="text"  onBlur={c=>this.input=c}  ref='input2'/> 
          </div>
        )
      }
    }
    ReactDOM.render(<Demo/>, document.getElementById('root'))
  </script>
</body>

</html>

回调形式的ref执行了几次?

如果回调形式的ref是以上面的行内(内联)方式定义的,他在(render调用第二次的时候)更新过程中会被执行两次,第一次是null,第二次返回参数的dom元素

3.class类型的ref

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>字符串ref</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class Demo extends React.Component {
      btn=()=>{
        alert(`${this.input3.value}`)
      }
      btn2=()=>{
        const {input2}=this
        alert(`${input2.value}`)
      }
      //class
      saveInput=(c)=>{
        this.input3=c
      }
      render() {
        return (
          <div>
            <input type="text"  ref={this.saveInput}/> 
        {   /* <input type="text"  ref={(c)=>{this.input=c}}/> */}
            <button onClick={this.btn}>点击获取左边的input值</button>
             简写形式
            <input type="text"  onBlur={c=>this.input=c}  ref='input2'/> 
          </div>
        )
      }
    }
    ReactDOM.render(<Demo/>, document.getElementById('root'))
  </script>
</body>

</html>

4.React.createRef创建ref(官方最推荐)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>字符串ref</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class Demo extends React.Component {
      myRef=React.createRef()
      myLeftRef=React.createRef()
      btn=()=>{
        console.log(this.myRef,55);
        alert(`${this.myRef.current.value}`)
      }
      leftBtn=()=>{
        alert(this.myLeftRef.current.value);
      }
      //class
      render() {
        return (
          <div>
            <input type="text"    ref={this.myLeftRef}/> 
        {   /* <input type="text"  ref={(c)=>{this.input=c}}/> */}
            <button onClick={this.leftBtn}>点击获取左边的input值</button>
             简写形式
            <input type="text"  onBlur={this.btn}  ref={this.myRef}/> 
          </div>
        )
      }
    }
    ReactDOM.render(<Demo/>, document.getElementById('root'))
  </script>
</body>

</html>

React的事件处理(为什么onClick的C要大写)

(1).通过onXxxx指定的事件处理函数

        react使用的是自定义(合成)事件,而不是dom的原生事件-----为了更好的兼容性

        react中的事件是通过事件委托方式处理的(委托给最外层的元素)------为了高效

(2).通过event.target得到发生事件的dom元素对象。----不要过渡使用ref

受控组件和非受控组件

非受控

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>非受控组件</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class LoginFrom extends React.Component {
      saveSubmit=(e)=>{
        e.preventDefault();
        console.log(e);
        alert(`用户名是${this.username.value},密码是${this.password.value}`)
      }
      render() {
        return (
          <div>
            <form  onSubmit={this.saveSubmit}>
              用户名:<input type="text" name='username' ref={(c)=>{this.username=c}} /> <br />
              密码:  <input type='password' name='password' ref={c=>this.password=c}  /> <br />
              <button>登录</button>
            </form>
          </div>
        )
      }
    }
    ReactDOM.render(<LoginFrom />, document.getElementById('root'))
  </script>
</body>

</html>

受控

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>受控组件</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class LoginFrom extends React.Component {
      state={
        usernameValue:'',
        passwordValue:''
      }
      saveSubmit=(e)=>{
        const {usernameValue,passwordValue}=this.state
        e.preventDefault();
        alert(`用户名是${usernameValue},密码是${passwordValue}`)
      }
      username=(e)=>{
        const {usernameValue}=this.state
        this.setState({
          usernameValue:e.target.value
        })
      }
      password=(e)=>{
        const {passwordValue}=this.state
        this.setState({
          passwordValue:e.target.value
        })
      }
      render() {
        return (
          <div>
            <form  onSubmit={this.saveSubmit}>
              用户名:<input type="text" name='username' onChange={this.username}  /> <br />
              密码:  <input type='password' name='password'  onChange={this.password}  /> <br />
              <button>登录</button>
            </form>
          </div>
        )
      }
    }
    ReactDOM.render(<LoginFrom />, document.getElementById('root'))
  </script>
</body>

</html>

区别:

受控组件是指输入类的dom,随着你的输入维护到状态里(state)待到用的时候从状态(state)里取,可以理解为vue的双向绑定v-model。

非受控组件是指在需要获取输入类dom输入的内容时,通过(在点击事件时现用现取)获取dom或者ref现用现取的方式拿到输入的值,被称为非受控组件。

高级函数--函数的柯里化

什么是高阶函数?

如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

1.若A函数,接收的参数是一个函数,那么该函数就是高阶函数。

2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数

(定时器,map)

什么是函数柯里化?

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

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>柯里化</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class LoginFrom extends React.Component {
      state = {
        usernameValue: '',
        passwordValue: ''
      }
      saveSubmit = (e) => {
        const { usernameValue, passwordValue } = this.state
        e.preventDefault();
        alert(`用户名是${usernameValue},密码是${passwordValue}`)
      }
      saveFrom = (dataType) => {
        const { usernameValue, passwordValue } = this.state
        return (e) => {
          this.setState({
            [dataType]: e.target.value
          })
        }
      }
      render() {
        return (
          <div>
            <form onSubmit={this.saveSubmit}>
              用户名:<input type="text" name='username' onChange={this.saveFrom('usernameValue')} /> <br />
              密码:  <input type='password' name='password' onChange={this.saveFrom('passwordValue')} /> <br />
              <button>登录</button>
            </form>
          </div>
        )
      }
    }
    ReactDOM.render(<LoginFrom />, document.getElementById('root'))
  </script>
</body>

</html>

柯里化

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>函数柯里化</title>
</head>

<body>
  <script>
    function num(a) {
      return (b) => {
        return (c) => {
          return a + b + c
        }
      }
    }
    console.log(num(3)(4)(5));

  </script>
</body>

</html>

不用函数柯里化实现

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>不用函数柯里化</title>
</head>

<body>
  <div id="root"></div>
  <div id="root2"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class LoginFrom extends React.Component {
      state = {
        usernameValue: '',
        passwordValue: ''
      }
      saveSubmit = (e) => {
        const { usernameValue, passwordValue } = this.state
        e.preventDefault();
        alert(`用户名是${usernameValue},密码是${passwordValue}`)
      }
      saveFrom = (dataType, e) => {
        const { usernameValue, passwordValue } = this.state
        this.setState({
          [dataType]: e.target.value
        })
      }
      render() {
        return (
          <div>
            <form onSubmit={this.saveSubmit}>
              用户名:<input type="text" name='username' onChange={(e) => { this.saveFrom('usernameValue', e) }} /> <br />
              密码:  <input type='password' name='password' onChange={e => this.saveFrom('passwordValue', e)} /> <br />
              <button>登录</button>
            </form>
          </div>
        )
      }
    }
    ReactDOM.render(<LoginFrom />, document.getElementById('root'))
  </script>
</body>

</html>

生命周期

(旧版本)

 红色的是挂载,蓝色的是更新,下面蓝色的是将要销毁

挂载

componentWillMount (组件将要挂载)

componentDidMount   (组件挂载完毕)(常用,开启定时器,发起网络请求,消息订阅)

componentWillUnmount (组件将要卸载)(常用,关闭定时器,取消消息订阅)

更新

先从setState那条线开始

  //控制组件更新的"阀门" 为true允许更新代码继续往下走,为false后面就不会执行了
      shouldComponentUpdate(){
        return true
      }

componentWillUpdate  组件将要更新的钩子

componentDidUpdate   组件更新完毕

再从forceUpdate那条线

最后从父组件传参componentWillReceiveProps开始走

componentWillReceiveProps:传的值下一次更新时触发,第一次不触发

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>生命周期</title>
</head>

<body>
  <div id="root"></div>
  <script src="../依赖包/旧版本/react.development.js"></script>
  <script src="../依赖包/旧版本/react-dom.development.js"></script>
  <script src="../依赖包/旧版本/babel.min.js"></script>
  <script src="../依赖包/旧版本/prop-types.js"></script>
  <script type="text/babel">
    class A extends React.Component {
      state = {
        car: '奔驰'
      }
      changeCar = () => {
        let { car } = this.state
        this.setState({
          car: '奥迪'
        })
      }
      render() {
        const { car } = this.state
        return (
          <div>
            <div>A组件----{car}</div>
            <button onClick={this.changeCar}>更改车名字</button>
            <B car={car} />
          </div>
        )
      }
    }
    class B extends React.Component {
      componentWillReceiveProps() {
        console.log('B-----componentWillReceiveProps');
      }
      render() {
        console.log(this.props);
        let { car } = this.props
        return (
          <div>{car}</div>
        )
      }
    }
    ReactDOM.render(<A />, document.getElementById('root'))
  </script>
</body>

</html>

(新版本)

废弃了三个钩子

componentWillMount (组件将要挂载)使用需要(UNSAFE_componentWillMount)

componentWillReceiveProps:传的值下一次更新时触发,第一次不触发。  使用需要(UNSAFE_componentWillReceiveProps)

componentWillUpdate  组件将要更新的钩子。使用需要(UNSAFE_componentWillUpdate)

先从挂载开始

对比多了个getDerivedStateFromProps,他返回一个对象

  static getDerivedStateFromProps(props, state){
      console.log('getDerivedStateFromProps',props, state);
      //return {opacity:10}
      return state

      }

如果返回的对象的key等于state里的key,那state里的那个key就永远没法更新了,并且能接收两个参数第一个参数props,组件传过来的值,第二个参数state,组件里的状态(state)。(用处如果你希望你组件里的状态无论在什么时候(初始化,或者修改)都取决于props,不会被改变了就用。)

getSnapshotBeforeUpdate(会把返回值当参数传给componentDidUpdate)

componentDidUpdate有三个参数('上一次的props','上一次的state','getSnapshotBeforeUpdate的返回值')

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>生命周期</title>
  <style>
    .item {
      height: 30px;
      width: 150px;
      background: yellow;
    }

    .arr {
      height: 210px;
      overflow: auto;
    }
  </style>
</head>

<body>
  <div id="root"></div>
  <script src="../依赖包/新版本/react.development.js"></script>
  <script src="../依赖包/新版本/react-dom.development.js"></script>
  <script src="../依赖包/新版本/babel.min.js"></script>
  <script src="../依赖包/新版本/prop-types.js"></script>
  <script type="text/babel">
    class Home extends React.Component {
      state = {
        count: 1
      }
      add=()=>{
        let { count } = this.state
        this.setState({
          count:count+1
        })
      }
       getSnapshotBeforeUpdate(state,props){
        return null

      }
      componentDidUpdate(preProps,preState,height){
        console.log(preProps,preState,height,999);
    
      }
      componentDidMount() {
        console.log('componentDidMount');
      }
      render() {
        let { count } = this.state
        return (
          <div>
           <button onClick={this.add}>点击加一</button> {count}
          </div>
        )
      }
    }
    ReactDOM.render(<Home opacity={30} count={21} />, document.getElementById('root'))
  </script>
</body>

</html>

实战

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>生命周期</title>
  <style>
    .item {
      height: 30px;
      width: 150px;
      background: yellow;
    }

    .arr {
      height: 210px;
      overflow: auto;
    }
  </style>
</head>

<body>
  <div id="root"></div>
  <script src="../依赖包/新版本/react.development.js"></script>
  <script src="../依赖包/新版本/react-dom.development.js"></script>
  <script src="../依赖包/新版本/babel.min.js"></script>
  <script src="../依赖包/新版本/prop-types.js"></script>
  <script type="text/babel">
    class Home extends React.Component {
      state = {
        homeList: []
      }
       getSnapshotBeforeUpdate(state,props){
        return this.refs.list.scrollHeight

      }
      componentDidUpdate(preProps,preState,height){
        this.refs.list.scrollTop+=this.refs.list.scrollHeight-height
      }
      componentDidMount() {
        setInterval(() => {
          let { homeList } = this.state
          let news = '作业' + (homeList.length + 1)
          this.setState({
            homeList: [news, ...homeList]
          })
        }, 500);
        console.log('componentDidMount');
      }
      render() {
        let { homeList } = this.state
        return (
          <div>
            <ul className='arr' ref='list'>
              {
                homeList.map((item, index) => {
                  return <li className='item' key={index}>{item}</li>
                })
              }
            </ul>
          </div>
        )
      }
    }
    ReactDOM.render(<Home opacity={30} count={21} />, document.getElementById('root'))
  </script>
</body>

</html>

脚手架

1.全局安装  npm i create-react-app  -g

2.找到需要创建的目录执行  create-react-app 文件名

3.public/index.html详解

4.当引入的两个组件类名一样时,会出现后者覆盖前者的问题

 

 解决:

消息订阅与发布

1.npm install pubsub-js

2.在需要用到的地方引入

import PubSub from 'pubsub-js'

3.发布数据

PubSub.publish("data", { data: [], isLoding: true, isSearch: false });

4.接收

  componentDidMount(){
   this.token= PubSub.subscribe('data',(msg,data)=>{
    this.setState({
      dataArr:data.data,
      isSearch:data.isSearch,
      isLoding:data.isLoding,
      err:data.err,
    })
    })
  }

卸载

  componentWillUnmount(){
    PubSub.unsubscribe(this.token)
  }

fetch

    fetch(`/api1/search/users2?q=${this.searchDom.value}`).then((response) => {
      // console.log(response);
      return response.json()
    },error=>{
      // console.log(error);
    }).then(res=>{
      console.log(res);
    },error=>{
      console.log(error);
    })

优化

    fetch(`/api1/search/users2?q=${this.searchDom.value}`).then((response) => {
      // console.log(response);
      return response.json()
    }).then(res=>{
      console.log(res);
    }).catch(err=>{
      console.log(err);
    })

在优化

  btn = async () => {
    PubSub.publish("data", { data: [], isLoding: true, isSearch: false });
    try {
      const response = await fetch(
        `/api1/search/users2?q=${this.searchDom.value}`
      );
      const data = await response.json();
      PubSub.publish("data", {
        data: data.items,
        isSearch: false,
        isLoding: false,
      });
      console.log(data);
    } catch (error) {
      PubSub.publish("data", {
        data: [],
        isLoding: false,
        isSearch: false,
        err: error.message,
      });
      console.log(error);
    }

路由(route)

1.安装 npm install  react-router-dom@5  

2021年11月升级到6版本   npm install  react-router-dom@6

以下是5版本

声明式

Link:导航区-路由的跳转链接类似于html的a链接,标签里有个to属性用来执行跳转到哪个路由。

Route:展示区-注册路由,标签里有两个属性,path:路径,根据路径匹配,如果匹配上了展示组件,component:代表跳到哪个路由组件里

import React, { Component } from 'react'
import './App.css'
import Home from './components/home'
import About from './components/about'
import { Link, Route } from 'react-router-dom'
export default class App extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
          </div>
        </div>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              {/* 在React中靠路由链接实现切换组件---编写路由链接  */}
              <Link className='list-group-item' to={'/about'}>About</Link>
              <Link className='list-group-item' to={'/home'}>Home</Link>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              {/* 注册路由 */}
              <Route path='/about' component={About} />
              <Route path='/home' component={Home} />
            </div>
          </div>
        </div>
      </div>
    )
  }
}

BrowserRouter:  路由类型,指定路由为哪种类型,因为使用路由必须要指定路由类型,这里包在index.js里是因为所有路由都来自于这个app组件一劳永逸,BrowserRouter:地址栏不带#

HashRouter:路由类型,带#,后面跟着的是hash值,特点:#号后面的东西都不会做为资源发送给服务器

 细节问题:你写的link被解析成a标签用,把里面的to换成了herf实现监听对路由的跳转

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

一般组件:引入后直接调用,不需要使用Route 里面的component属性,如果不传递参数props为空对象,并且传什么,props里就有什么,存放位置也不同,放在components文件夹里

路由组件:引入后需要结合<Route  component>使用,存放位置也不同,放在pages文件夹里,默认的props里有三个值history,location,match。

        

NavLink:控制按钮高亮,标签属性activeClassName,默认是active

import React, { Component } from 'react'
import './App.css'
import Home from './components/home'
import About from './components/about'
import { NavLink, Route } from 'react-router-dom'
export default class App extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
          </div>
        </div>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              {/* 在React中靠路由链接实现切换组件---编写路由链接  */}
              <NavLink activeClassName='a' className='list-group-item' to={'/about'}>About</NavLink>
              <NavLink activeClassName='a' className='list-group-item' to={'/home'}>Home</NavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              {/* 注册路由 */}
              <Route path='/about' component={About} />
              <Route path='/home' component={Home} />
            </div>
          </div>
        </div>
      </div>
    )
  }
}

 

封装NavLink

import React, { Component } from 'react'
import './App.css'
import Home from './pages/home'
import About from './pages/about'
import { Route } from 'react-router-dom'
import  MyNavLink  from './components/MyNavLink/index'
export default class App extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
          </div>
        </div>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              {/* 在React中靠路由链接实现切换组件---编写路由链接  */}
              <MyNavLink to={'/about'}>About</MyNavLink>
              <MyNavLink to={'/home'}>Home</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              {/* 注册路由 */}
              <Route path='/about' component={About} />
              <Route path='/home' component={Home} />
            </div>
          </div>
        </div>
      </div>
    )
  }
}

MyNavLink(标签内容会通过this.props.children)拿到并放在标签内容里

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

    );
  }
}

Switch(通常情况下,path和component是一一对应的关系,Switch可以提高路由匹配的效率(单一匹配))

解决index.html里面样式丢失

exact:开启路由的严格匹配

replace:开启路由的replace模式不留下痕迹,默认不写是push

Redirect:重定向 (一般写在路由最下方,当所有路由都无法匹配时,跳转的到Redirect指定的路由)

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

嵌套路由(二级,多级路由)

import React, { Component } from "react";
import News from "./components/news";
import Message from "./components/message";
import { Switch, Route,Redirect } from "react-router-dom";
import MyNavLink from "../../components/MyNavLink";
export default class Home extends Component {
  render() {
    return (
      <div className="panel-body">
        <h3>我是Home的内容</h3>
        <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>
            <Route path="/home/message" component={Message}></Route>
            <Redirect to='/home/news'></Redirect>
          </Switch>
        </div>
      </div>
    );
  }
}

注册子路由时要写上父路由的path值,路由的匹配是按照注册路由的顺序进行的

路由参数传递

params

向路由组件传递params参数

 <Link to={`/home/message/content/${item.title}/${item.id}/${item.content}`}>
    {item.title}
  </Link>

声明接收params参数

        <Switch>
          <Route
            path="/home/message/content/:title/:id/:content"
            component={Content}
          ></Route>
        </Switch>

接收

    const {id,title,content}=this.props.match.params

例子:

import React, { Component } from "react";
import Content from "./component/content";
import { Link, Switch, Route } from "react-router-dom";
export default class message extends Component {
  state = {
    messageList: [
      {
        title: "消息1",
        id: 1,
        content: "战胜新冠",
      },
      {
        title: "消息2",
        id: 2,
        content: "新冠必败",
      },
      {
        title: "消息3",
        id: 3,
        content: "傻逼新冠",
      },
    ],
  };
  render() {
    const { messageList } = this.state;
    return (
      <div>
        <div>
          <ul>
            {messageList.map((item) => {
              return (
                <li key={item.id}>
                  {/* 向路由组件传递params参数 */}
                  <Link
                    to={`/home/message/content/${item.title}/${item.id}/${item.content}`}
                  >
                    {item.title}
                  </Link>
                </li>
              );
            })}
          </ul>
        </div>
        <hr></hr>
        <Switch>
          {/* 声明接收params参数 */}
          <Route
            path="/home/message/content/:title/:id/:content"
            component={Content}
          ></Route>
        </Switch>
      </div>
    );
  }
}

接收:

import React, { Component } from "react";

export default class content extends Component {
  render() {
    const {id,title,content}=this.props.match.params
    return (
      <div className="content">
        <ul>
          <li>id:{id}</li>
          <li>title:{title}</li>
          <li>content:{content}</li>
        </ul>
      </div>
    );
  }
}

地址栏样式:http://localhost:3000/home/message/content/消息2/2

search(query)

向路由组件传递search参数

<Link to={`/home/message/content/?id=${item.id}&title=${item.title}`}>
 {item.title}
</Link>

search参数无需声明接受

       <Switch>
          {/* search参数无需声明接受 */}
          <Route path="/home/message/content" component={Content}></Route>
        </Switch>

search接受参数

this.props.location.search
//search: "?id=1&title=%E6%B6%88%E6%81%AF1"

需要引入一个插件把他转成对象

//安装 npm i qs

//import qs from "qs";

const { search } = this.props.location;

const result = qs.parse(search.slice(1));

 地址栏样式:http://localhost:3000/home/message/content/?id=1&title=消息1

state

向路由组件传递state参数

 <Link to={{pathname:'/home/message/content',state:{id:item.id,title:item.title}}}>
                    {item.title}
  </Link>

state参数无需声明接受

  <Switch>
     <Route path="/home/message/content" component={Content}></Route>
  </Switch>

state接受参数

  const { id, title } = this.props.location.state||{};

  地址栏样式:http://localhost:3000/home/message/content

因为没在导航看显示,所以每次点击history就会把他存起了,所以刷新也不会丢,但如果你把历史都清了,this.props.location.state就拿不到值了,所以要加个||{}以免报错。

总结

编程式 

params

import React, { Component } from "react";
import Content from "./component/content";
import { Link, Switch, Route } from "react-router-dom";
export default class message extends Component {
  state = {
    messageList: [
      {
        title: "消息1",
        id: 1,
      },
      {
        title: "消息2",
        id: 2,
      },
      {
        title: "消息3",
        id: 3,
      },
    ],
  };
  render() {
    const { messageList } = this.state;
    return (
      <div>
        <div>
          <ul>
            {messageList.map((item) => {
              return (
                <li key={item.id}>
                  {/* 向路由组件传递search参数 */}
                  <Link
                    to={`/home/message/content/?id=${item.id}&title=${item.title}`}
                  >
                    {item.title}
                  </Link>
                  <button
                    onClick={() => {
                      this.pushShow(item.id, item.title);
                    }}
                  >
                    push
                  </button>
                  <button
                    onClick={() => {
                      this.replaceShow(item.id, item.title);
                    }}
                  >
                    replace
                  </button>
                </li>
              );
            })}
          </ul>
        </div>
        <hr></hr>
        {/* 需要接受 */}
        <Switch>
          <Route
            path="/home/message/content/:id/:title"
            component={Content}
          ></Route>
        </Switch>
      </div>
    );
  }
  pushShow = (id, title) => {
    this.props.history.push(`/home/message/content/${id}/${title}`);
  };
  replaceShow = (id, title) => {
    this.props.history.replace(`/home/message/content/${id}/${title}`);
  };
}

search(修改按钮里面的)

    this.props.history.push(`/home/message/content/?id=${id}&title=${title}`);

state

import React, { Component } from "react";
import Content from "./component/content";
import { Link, Switch, Route } from "react-router-dom";
export default class message extends Component {
  state = {
    messageList: [
      {
        title: "消息1",
        id: 1,
      },
      {
        title: "消息2",
        id: 2,
      },
      {
        title: "消息3",
        id: 3,
      },
    ],
  };
  render() {
    const { messageList } = this.state;
    return (
      <div>
        <div>
          <ul>
            {messageList.map((item) => {
              return (
                <li key={item.id}>
                  {/* 向路由组件传递search参数 */}
                  <Link
                    to={`/home/message/content/?id=${item.id}&title=${item.title}`}
                  >
                    {item.title}
                  </Link>
                  <button
                    onClick={() => {
                      this.pushShow(item.id, item.title);
                    }}
                  >
                    push
                  </button>
                  <button
                    onClick={() => {
                      this.replaceShow(item.id, item.title);
                    }}
                  >
                    replace
                  </button>
                </li>
              );
            })}
          </ul>
        </div>
        <hr></hr>
        <Switch>
          <Route
            path="/home/message/content"
            component={Content}
          ></Route>
        </Switch>
      </div>
    );
  }
  pushShow = (id, title) => {
    this.props.history.push(`/home/message/content`,{id:id,title:title});
  };
  replaceShow = (id, title) => {
    this.props.history.replace(`/home/message/content`,{id:id,title:title});
  };
}

withRouter(针对一般组件身上没有路由组件身上的api,history这个方法,但又想实现跳转功能,很重要)

import React, { Component } from "react";
import {withRouter} from 'react-router-dom'
class Header extends Component {
  render() {
    return (
      <div className="col-xs-offset-2 col-xs-8">
        <div className="page-header">
          <h2>React Router Demo</h2>
          <button onClick={this.goBack}>前进</button>
          <button onClick={this.goHach}>回退</button>
        </div>
      </div>
    );
  }
  goBack=()=>{
    console.log(this.props);
  }
  goHach=()=>{
    
  }
}
export default  withRouter(Header)

HashRouter与BrowserRouter的区别

redux(react的状态管理类似于vue的vuex)

安装 npm install --save redux react-redux

mini版

1.创建一个store.js

//1.引入createStore,用于创建redux中最为核心的store对象
import {legacy_createStore as createStore} from 'redux'
//引入为conut组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)

2.创建一个count_reducer.js

//1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数。
//2.reducer函数会接收两个参数,分别是之前的状态(preState),动作对象(action)
const initState=0
export default function countReducer(preState=initState, action) {
  //从action对象中获取:type,data
  const { type, data } = action
  //根据type决定
  switch (type) {
    case 'add':
      return preState + data
    case 'minus':
      return preState - data
    default:
      return  preState
  }
}

3.页面引入

//用于获取redux中的状态
import store from "../../redux/store";

初始化状态

store.getState()

指定操作类型和操作的值

 store.dispatch({type:'add',data:parseInt(this.selectValue.value)})

完整代码

import React, { Component } from "react";
import { Button } from "antd";
//用于获取redux中的状态
import store from "../../redux/store";
export default class ReduxTc extends Component {
  state = {
    value: 0,
  };
  render() {
    return (
      <div>
        <div>click {store.getState()} times</div>
        <select ref={(e)=>{this.selectValue=e}}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <Button onClick={this.Add}>加</Button>
        <Button onClick={this.minus}>减</Button>
        <Button onClick={this.odd}>奇数才加</Button>
        <Button onClick={this.asyncAdd}>异步加</Button>
      </div>
    );
  }
  Add = () => {
    store.dispatch({type:'add',data:parseInt(this.selectValue.value)})
  };
  minus = () => {
    store.dispatch({type:'minus',data:parseInt(this.selectValue.value)})
  };
  odd = () => {
    if (store.getState() % 2 !== 0) {
      store.dispatch({type:'add',data:parseInt(this.selectValue.value)})
    }
  };
  asyncAdd = () => {
    setTimeout(() => {
      store.dispatch({type:'add',data:parseInt(this.selectValue.value)})
    }, 2000);
  };
}

因为redux只负责管理状态不负责刷新render

所以在index.js里利用store.subscribe更新

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom'
import App from './App';
import store from './redux/store';
import { ConfigProvider } from 'antd';
const root = ReactDOM.createRoot(document.getElementById('root'));
store.subscribe(()=>{
root.render(
  <React.StrictMode>
    <Router>
      <ConfigProvider
        theme={{
          token: {
            colorPrimary: 'red',
          },
        }}
      >
        <App />
      </ConfigProvider>
    </Router>
  </React.StrictMode>
);
})
root.render(
  <React.StrictMode>
    <Router>
      <ConfigProvider
        theme={{
          token: {
            colorPrimary: 'red',
          },
        }}
      >
        <App />
      </ConfigProvider>
    </Router>
  </React.StrictMode>
);

完整版

增加了action(用来存放操作类型的)

用来避免类型写错

reducer 用来操作和初始化数据的

store 管理状态的仓库 

用来刷新render

 reduxTc.jsx

import React, { Component } from "react";
import { Button } from "antd";
//用于获取redux中的状态
import store from "../../redux/store";
//引入action
import { createAdd,createMinus } from "../../redux/conut_action";
export default class ReduxTc extends Component {
  state = {
    value: 0,
  };
  render() {
    return (
      <div>
        <div>click {store.getState()} times</div>
        <select ref={(e)=>{this.selectValue=e}}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <Button onClick={this.Add}>加</Button>
        <Button onClick={this.minus}>减</Button>
        <Button onClick={this.odd}>奇数才加</Button>
        <Button onClick={this.asyncAdd}>异步加</Button>
      </div>
    );
  }
  Add = () => {
    store.dispatch(createAdd(this.selectValue.value*1))
  };
  minus = () => {
    store.dispatch(createMinus(this.selectValue.value*1))
  };
  odd = () => {
    if (store.getState() % 2 !== 0) {
      store.dispatch(createAdd(this.selectValue.value*1))
    }
  };
  asyncAdd = () => {
    setTimeout(() => {
      store.dispatch(createAdd(this.selectValue.value*1))
    }, 2000);
  };
}

异步action

需要安装插件

npm install redux-thunk

count_action.js (添加异步action)

 conutant.js(操作类型不变)

 count_reducer.js(初始化reducer和操作不变)

 

 store.js(添加applyMiddleware用于执行中间件thunk)

 redux_tc.jsx

import React, { Component } from "react";
import { Button } from "antd";
//用于获取redux中的状态
import store from "../../redux/store";
//引入action
import { createAdd,createMinus,createAsyncAdd } from "../../redux/conut_action";
export default class ReduxTc extends Component {
  state = {
    value: 0,
  };
  render() {
    return (
      <div>
        <div>click {store.getState()} times</div>
        <select ref={(e)=>{this.selectValue=e}}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>
        <Button onClick={this.Add}>加</Button>
        <Button onClick={this.minus}>减</Button>
        <Button onClick={this.odd}>奇数才加</Button>
        <Button onClick={this.asyncAdd}>异步加</Button>
      </div>
    );
  }
  Add = () => {
    store.dispatch(createAdd(this.selectValue.value*1))
  };
  minus = () => {
    store.dispatch(createMinus(this.selectValue.value*1))
  };
  odd = () => {
    if (store.getState() % 2 !== 0) {
      store.dispatch(createAdd(this.selectValue.value*1))
    }
  };
  //使用异步action方法就行了
  asyncAdd = () => {
    // setTimeout(() => {
      store.dispatch(createAsyncAdd(this.selectValue.value*1,500))
    // }, 2000);
  };
}

react-redux

安装 npm install react-redux

redux开发者工具使用

让redux工具亮

1. npm install redux-devtools-extension

2.引入import {composeWithDevTools} from 'redux-devtools-extension'

react拓展知识

this.setState(对象式写法),react在更新状态的时候其实是一个异步的更新,这就导致以下的写法拿不到更新后的值。

所以要传一个回调,在回调里拿更新值 

import React, { Component } from 'react'

export default class Demo extends Component {
  state={
    count:0
  }
  render() {
    const {count}=this.state
    return (
      <div>
        <h1>当前求和为{count}</h1>
        <button onClick={this.add}>加</button>
      </div>
    )
  }
  add=()=>{
    //对象式
    const {count}=this.state
    this.setState({count:count+1},()=>{
      console.log(this.state.count);//捕获每次更新状态的值
    })
  }
}

函数式写法 

import React, { Component } from 'react'

export default class Demo extends Component {
  state={
    count:0
  }
  render() {
    const {count}=this.state
    return (
      <div>
        <h1>当前求和为{count}</h1>
        <button onClick={this.add}>加</button>
      </div>
    )
  }
  add=()=>{
    //函数式
    this.setState((state,props)=>{
      return {count:state.count+1}
    },()=>{
      console.log(this.state.count);
    })
  }
}

 lazyLoad(路由懒加载)

将原来的引入方式注释掉改用const Home=lazy(()=>import('./pages/home'))

从react里引入lazy,Suspense

import React, { Component,lazy,Suspense } from 'react'
import './App.css'
import { Route, Switch, Redirect } from 'react-router-dom'
import MyNavLink from './components/MyNavLink/index'
// import Home from './pages/home'
// import About from './pages/about'
const Home=lazy(()=>import('./pages/home'))
const About=lazy(()=>import('./pages/about'))
export default class App extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
          </div>
        </div>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              {/* 在React中靠路由链接实现切换组件---编写路由链接  */}
              <MyNavLink to={'/about'}>About</MyNavLink>
              <MyNavLink to={'/home'}>Home</MyNavLink>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              {/* 注册路由 */}
              <Suspense fallback={<h1>loding.....</h1>}>
              <Switch>
                <Route path='/about' component={About} />
                <Route path='/home' component={Home} />
                <Redirect to='/about' />
              </Switch>
              </Suspense>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

Hook(让函数式组件可以修改自己状态)

1.const [state,setState]=React.useState(0)

state初始化状态,setState可修改的方法

import React from "react";
export default function Demo() {
  const [conut,setCount]=React.useState(0)
  const add=()=>{
    setCount(conut+1)
  }
  return (
    <div>
      <h1>当前求和为{conut}</h1>
      <button onClick={add}>加</button>
    </div>
  );
}

 2.React.useEffect

  React.useEffect(()=>{
  },[])

接受两个参数

不传默认监听所有变化(相当于componentDidMount和componentDidUpdate),放一个空数组相当于componentDidMount,return返回一个函数相当于 componentWillUnmount。

import React from "react";
import ReactDOM  from "react-dom";
import {root} from '../../index'
export default function Demo() {
  const [conut,setCount]=React.useState(0)
  const add=()=>{
    setCount(conut+1)
  }
  const unmount=()=>{
    root.unmount()

  }
  React.useEffect(()=>{
  let timer=  setInterval(()=>{
      console.log('@');
      setCount(conut+1)
      // setCount(conut=>conut+1)
    },1000)
    return ()=>{
      clearInterval(timer)
      console.log('###');
    }
  },[])
  return (
    <div>
      <h1>当前求和为{conut}</h1>
      <button onClick={add}>加</button>
      <button onClick={unmount}>卸载</button>
    </div>
  );
}

RefHook

import React from "react";
import {root} from '../../index'
export default function Demo() {
  const myref=React.useRef()
  const [conut,setCount]=React.useState(0)
  const add=()=>{
    console.log(myref.current.value);
    setCount(conut+1)
  }
  const unmount=()=>{
    root.unmount()

  }
  return (
    <div>
      <h1>当前求和为{conut}</h1>
      <input type="text" name="" id=""  ref={myref} />
      <button onClick={add}>加</button>
      <button onClick={unmount}>卸载</button>
    </div>
  );
}

Fragment(vue3提出的一样减少teplete)

import React,{Fragment} from "react";
import {root} from '../../index'
export default function Demo() {
  const myref=React.useRef()
  const [conut,setCount]=React.useState(0)
  const add=()=>{
    console.log(myref.current.value);
    setCount(conut+1)
  }
  const unmount=()=>{
    root.unmount()

  }
  return (
    <Fragment>
      <h1>当前求和为{conut}</h1>
      <input type="text" name="" id=""  ref={myref} />
      <button onClick={add}>加</button>
      <button onClick={unmount}>卸载</button>
    </Fragment>
  );
}

context(用于祖孙传值)

import React, { Component } from "react";
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
export default class A extends Component {
  state = {
    username: "小明",
    age: 18,
  };
  render() {
    const { username, age } = this.state;
    return (
      <div>
        <h1>
          我是A组件 {username}=={age}
        </h1>
        我的儿子是
        <Provider value={{ username, age }}>
          <B />
        </Provider>
      </div>
    );
  }
}
class B extends Component {
  //类似组件接受
  static contextType = MyContext;
  render() {
    const { username, age } = this.context;
    return (
      <div>
        <h2>我是B组件{username}</h2>
        我的儿子是 <C />
      </div>
    );
  }
}
function C() {
  //函数组件接受
  return (
    <h3>
      我是C组件
      <Consumer>
        {(value) => {
          return `${value.username}`;
        }}
      </Consumer>
    </h3>
  );
}

优化

由于有时候this.setState更新东西没更新到子组件用到的属性,也会刷新子组件的render,导致性能问题这里把引入的Component换成PureComponent,PureComponent重写了shouldComponentUpdate

import React, { PureComponent } from "react";
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
export default class A extends PureComponent {
  state = {
    username: "小明",
    age: 18,
  };
  render() {
    const { username, age } = this.state;
    return (
      <div>
        <h1>
          我是A组件 {username}=={age}
        </h1>
        我的儿子是
        <Provider value={{ username, age }}>
          <B />
        </Provider>
      </div>
    );
  }
}
class B extends PureComponent {
  //类似组件接受
  static contextType = MyContext;
  render() {
    const { username, age } = this.context;
    return (
      <div>
        <h2>我是B组件{username}</h2>
        我的儿子是 <C />
      </div>
    );
  }
}
function C() {
  //函数组件接受
  return (
    <h3>
      我是C组件
      <Consumer>
        {(value) => {
          return `${value.username}`;
        }}
      </Consumer>
    </h3>
  );
}

但是PureComponent有弊端

 引用地址相同时默认阀门为false

renderProps

import React, { Component, Fragment } from "react";

export default class Demo extends Component {
  render() {
    return (
      <Fragment>
        <h1>我是demo</h1>
        <hr />
        <A render={(name) => <B name={name} />} />
      </Fragment>
    );
  }
}
class A extends Component {
  state = {
    name: "tom",
  };
  render() {
    const { name } = this.state;
    return (
      <Fragment>
        <h1>我是A组件</h1>
        {this.props.render(name)}
      </Fragment>
    );
  }
}
class B extends Component {
  render() {
    return <Fragment>{this.props.name}</Fragment>;
  }
}

错误边界

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值