react笔记

223 阅读23分钟

class

class是es6的语法糖

什么叫做语法糖:便捷写法

最终还是会运行成es5构造函数语法

class Cat {
            constructor (kind) {
                this.kind = kind   // 这里的this指向的是实例上  挂载在实例上
            }
            act () {   //  这里的act挂载在原型上
                console.log('我是猫,我会叫!喵~ ~ ~')
            }
        }
        const p1 = new Cat('猫')
        p1.act()
        console.log(p1)

image-20220620131142302.png

方法的不同写法和this的指向:

class Cat {
            constructor (kind) {
                this.kind = kind   // 这里的this指向的是实例上  挂载唉实例上
            }
            act () {   //  这里的act挂载在原型上
                console.log('我是猫,我会叫!喵~ ~ ~,this的指向是')
                console.log(this)
            }
            test = () => {   //  挂载在实例上
                console.log('这是一个箭头函数,this的指向是')
                console.log(this)
            }
        }
        const p1 = new Cat('猫')
        p1.act()
        console.log(p1)

image-20220620131830670.png

继承

class Cat {
            constructor (kind) {
                this.kind = kind   // 这里的this指向的是实例上  挂载在实例上
            }
            act () {   //  这里的act挂载在原型上
                console.log('我是猫,我会叫!喵~ ~ ~')
            }
        }
        const p1 = new Cat('猫')
        p1.act()
        // console.log(p1)
        // 下面介绍一下继承
        class BlueCat extends Cat{
            constructor (name, age, kind) {
            //  super  代表调用父类的constructor
                super(kind)
                this.name = name
                this.age = age
            }
            actBlue () {
                console.log('我是蓝猫,我的毛发是蓝色的,我也会喵喵喵哦~ ~ ~')
            }
        }
        const p2 = new BlueCat('蓝色英短', 4 ,'猫')
        p2.actBlue()
        console.log(p2)

image-20220620133014880.png

静态属性

image-20220620133123002.png

image-20220620133406539.png

安装

npm i create-react-app -g

创建项目

create-react-app 项目名称

项目运行主要依赖包

react核心语法包(虚拟dom jsx)

react-dom(多个组件虚拟dom结构 编译 挂载到#root上)

react-scripts 隐藏webpack的配置

jsx

视图结构通过虚拟dom实现

vue 和 react都各自提供了友好的虚拟dom编写方式

vue通过vue-loader编译 单文件组件中的template标签中的结构

react通过在js中直接写标签 react包自动分析在js中的标签结构

react中在js中写的标签是虚拟dom对象

xml in js

xml 可以理解为自定义标签

原来用于前后端传输数据

jsx中使用js表达式

使用花括号包裹,在花括号中使用表达式

import ReactDOM from 'react-dom'
const msg  = '这是一个msg'
const tpl = (
  <h1>Hello, world!!!{msg}
  {/* 这是一个注释 */}
  </h1>
)
ReactDOM.render(
    tpl,
    document.getElementById('root')
  );
​

image-20220620151431146.png

如何在vscode中 打开在react的js中打开对于emmet语法支持

image-20220620152141252.png

记得重启

组件

vsCode安装

React-Native/React/Redux snippets for es6/es7

组件要求首字母大写

函数式函数有先天缺陷 没有内部状态 没有生命周期

函数式组件

import ReactDOM from 'react-dom'
const App = () => {
  return (
    <div>
      <h1>Hello, world!!!
  {/* 这是一个注释 */}
  </h1>
    </div>
  )
}
ReactDOM.render(
    <App/>,
    document.getElementById('root')
  );
​
import ReactDOM from 'react-dom'
const App = (props) => {
  console.log(props)
  return (
    <div>
      <h1>Hello, world!!!
  {/* 这是一个注释 */}
  </h1>
    </div>
  )
}
ReactDOM.render(
    <App title="这是一个title" num="这是一个number"/>,
    document.getElementById('root')
  );
​

image-20220620154156112.png

class 组件

新建一个class App

import React, { Component } from 'react'

class App extends Component {
    render(){
        console.log(this)
        return(
            <div>
                <h1>这是一个子组件</h1>
                // 拿到传过来的title值  渲染进页面
                
                <h2>{this.props.title}</h2>
            </div>
        )
    }
}
export default App

放入ReactDOM.render中显示

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

ReactDOM.render(
    <App title="这是一个title"/>,  //  相当于 new App() 得到组件实例  并通过实例调用render方法(返回虚拟dom)
    document.getElementById('root')
  );

class App中console.log(this)打印结果如下:

this指向的是App的实例

标签上传递的参数在实例的props上

image-20220620192109127.png

组件中 模板的样式处理

内联

通过对象的形式来定义style属性(原因:标签其实是虚拟dom对象)

使用时通过{ }

写样式的时候 宽高或者其他可以省略单位 例如:100px -> 100 100+100 -> (100+100)px

{ } 一个花括号的时候会被当作在js中写表达式

正确语法:

{{width:100,height:100+100,backgroundColor:"pink"}}
import React, { Component } from 'react'

class App extends Component {
    render(){
        console.log(this)
        return(
            <div>
                <h1>这是一个子组件</h1>
                <h2 style={{width:100,height:100+100,backgroundColor:"pink"}}>这是一个处理样式测试的 h2 </h2>
            </div>
        )
    }
}
export default App

image-20220620194031841.png

外部样式

css

新建一个css文件

image-20220620195650199.png

定义className="test"

import React, { Component } from 'react'

class App extends Component {
    render(){
        console.log(this)
        return(
            <div>
                <h1>这是一个子组件</h1>
                <h2 style={{width:100,height:100+100,backgroundColor:"pink"}}>这是一个处理样式测试的 h2 </h2>
                <div className="test"></div>
            </div>
        )
    }
}
export default App

引入

import ReactDOM from 'react-dom'
import App from './App'
//  引入
import './assets/css/a.css'
ReactDOM.render(
    <App title="这是一个title"/>,  //  相当于 new App() 得到组件实例  并通过实例调用render方法(返回虚拟dom)
    document.getElementById('root')
  );

显示

image-20220620195837504.png

sass

注意事项:react中自安装了一个node-sass,运行时会报错,解决方法:1 在package-lock.json中找到node-sass删除,然后删除node_modules文件夹,然后执行npm i重新下载所有依赖,然后下载npm i sass -D即可

新建一个scss文件

image-20220620200810792.png

然后

<div className="test01"></div>

在index.js中引入

import './assets/sass/b.scss'

运行显示

image-20220620201241116.png

styled-components基础使用

原理:

每一个区块的布局容器定义成样式组件

下载:

npm i styled-components -S

普通案例

新建一个js文件,定义盒子样式,并且导出

import styled from 'styled-components'

const Box = styled.div`
width:200px;
height:200px;
background:red;
`
export {
    Box
}

导入

render(){
        console.log(this)
        return(
            <div>
                <Box></Box>
            </div>
        )
    }

嵌套案例

定义导出Box01

const Box01 = styled.div`
width:200px;
height:200px;
background:red;
p{
    color:#fff;
}
`

导入使用

<Box01>
  <p>这是嵌套的p标签</p>
</Box01>

显示

image-20220620204156414.png

传递参数

<Box01  bgc="#000">
   <p>这是嵌套的p标签</p>
</Box01>
const Box01 = styled.div`
width:200px;
height:200px;
background:${props => props.bgc?props.bgc:'red'};
p{
    color:#fff;
}
`

image-20220620204511867.png

继承

const Box01 = styled.div`
width:200px;
height:200px;
background:${props => props.bgc?props.bgc:'red'};
p{
    color:#fff;
}
` 
const Box02 = styled(Box01)`
border:10px solid green
`
render(){
        console.log(this)
        return(
            <div>
                {/* <Box></Box> */}
                <Box01  bgc="#000">
                    <p>这是嵌套的p标签</p>
                </Box01>
                <Box02></Box02>
            </div>
        )
    }

image-20220620210235390.png

动画

const move = keyframes`
0%{
transform:rotate(45deg)
}
100%{
    transform:rotate(-45deg)
}
`

const Box01 = styled.div`
width:200px;
height:200px;
background:${props => props.bgc?props.bgc:'red'};
animation:${move} 1s infinite;
p{
    color:#fff;
}
`

jsx原理

react在js执行之前,会分析jsx结构,并调用react.createElement将jsx编译成虚拟dom

React.createElement('div',
{
className:"box",
id:"test"
},
[React.createElement('p',{className:"testSon01"},["这是p"]),
React.createElement('p',{},["这是p"])
])

props验证

安装

prop-types包

npm install --save prop-types
import PropTypes from 'prop-types';
class MyComponent extends React.Component {
  render() {
    // ... do things with the props
  }
}

具体验证规范

MyComponent.propTypes = {
    // 基础类型验证
    optionalArray: PropTypes.array,
    optionalBigInt: PropTypes.bigint,
    optionalBool: PropTypes.bool,
    optionalFunc: PropTypes.func,
    optionalNumber: PropTypes.number,
    optionalObject: PropTypes.object,
    optionalString: PropTypes.string,
    optionalSymbol: PropTypes.symbol,
//   必须是react组件  加标签  本质穿的是组件内部jsx
    optionalElement: PropTypes.element,
    // 必须时react组件   不加括号  使用时  <this.props.j/>
    optionalElementType: PropTypes.elementType,
    // 必须是对象   必须时Message的实例
    optionalMessage: PropTypes.instanceOf(Message),
    // 值必须是括号内多个中的一个
    optionalEnum: PropTypes.oneOf(['News', 'Photos']),
    optionalUnion: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
      PropTypes.instanceOf(Message)
    ]),
    // 必须时数字的数组
    optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
    // 对象里每一个属性都是数字
    optionalObjectOf: PropTypes.objectOf(PropTypes.number),
    // 修饰  属性符合规范
    optionalObjectWithShape: PropTypes.shape({
      optionalProperty: PropTypes.string,
      requiredProperty: PropTypes.number.isRequired
    }),
//   必须时对象  只能用给定属性   属性值必须复合要求
    optionalObjectWithStrictShape: PropTypes.exact({
      optionalProperty: PropTypes.string,
      requiredProperty: PropTypes.number.isRequired
    }),
//   链式调用   验证数据类型  同时必穿 适用于以上所有验证
    requiredFunc: PropTypes.func.isRequired,
  };

默认props

当 不传d值时,依然可以使用this.props.d 默认值为 'xxxxxx'

class App extends Component {
    render(){
        console.log(this)
        return(
            <div>
            111
            </div>
        )
    }
}

App.defaultProps={
    d:'xxxxxx'
}

props.children

相当于插槽

<App>
      <div>
        hello it is props.children!
      </div>
</App>
class App extends Component {
    render(){
        console.log(this)
        return(
            <div>
                {this.props.children}
            </div>
        )
    }
}

state

props组件数据管理 用于管理外部数据的属性

state属性用于管理内部数据

render(){ 
        this.state = {
            msg:'这是一个示例变量'
        }
        console.log(this)
        return(
            <div>
                {this.props.children}
                {this.state.msg}
            </div>
        )
    }

事件

class App extends Component {
    render(){ 
        this.state = {
            msg:'这是一个示例变量'
        }
        console.log(this)
        return(
            <div>
                {this.props.children}
                {this.state.msg}
                <button onClick={this.act}>点击事件</button>
                <button onClick={this.act01}>点击事件</button>
            </div>
        )
    }
    act = () => {   //  挂载实例上
        console.log(this)
    }

    act01 = function() {   //  挂载原型上
        console.log(this)
    }
}

输出

image-20220621111229669.png

setState修改状态刷新视图

react不是mvvm框架,刷新试图下面几种情况:

组件中某一个props改变 立即刷新

调用setState方法 修改组件state

强制刷新 this.forceUpdate()

打印act = () => { console.log(this) }

image-20220621113256171.png

import React, { Component } from 'react'
class App extends Component {
    constructor () {
        super()
        this.state = {
            msg:'这是一个示例变量',
            isBeatify:true
        }
    }
    render(){
        return(
            <div>
                {this.state.isBeatify?'这是一个美女':'这是一个好女孩'}
            </div>
        )
    }
    //  参数是函数
    act = () => {
        this.setState((state, props) => {
            return {
                isBeatify:!state.isBeatify
            }
        })
    }
    // 或者   参数是对象
    act = () => {
        this.setState({
        isBeatify:!this.state.isBeatify
        })
    }
}
export default App

setState修改数据和刷新视图的异步操作

setState修改数据值变化以及视图刷新有些场景是同步的有些是异步的

异步性能更高 大部分场景下是异步 只能react捕获不到场景下调用setState才是同步

以下场景是异步:react可以捕获到的操作就是异步

1 react自己的合成事件(例如:onClick)事件函数中调用是异步

2 react生命周期钩子函数中调用是异步的

以下场景是同步的:react捕获不到的操作(不是react语法)

1 原生的dom事件 dom.onclick

2 其他函数中 比如:定时器

从18.x开始都是异步的

当异步情况下如何想要立即获取修改后最新的值 或者获取最新视图更新的最新的dom该怎么办

setState第二个参数是回调函数

出发的时机:数据更新完成 dom更新完成后回调触发(就像vue的this.$nextTick)

调用setState修改数据后,想拿到最新的值或者最新的dom就在setState的第二个参数回调函数中获取即可

act = () => {
        this.setState((state, props) => {
            return {
                isBeatify:!state.isBeatify
            }
        },()=>{
            console.log(this.state.isBeatify)
        })
    }

对比props和state

props管理组件外部数据

state管理组件自己的数据

什么时候使用props 什么使用state

逻辑组件(路由组件)

ui组件(傻瓜组件 在逻辑组件中 使用子组件)

将所有的数据都放在逻辑组件中管理 业务代码都在逻辑组件实现

逻辑组件一般使用state

ui组件使用props

衍生的一些重要概念

单向数据流

父组件传递给子组件 子组件无法修改

状态提升

应该多个后代组件的数据 统统提升到父组件中管理

逻辑组件和ui组件

组件渲染数据语法

条件渲染

class Todo extends Component {
    constructor() {
        super();
        this.state = { 
            msg: '参数一',
            newMsg:'参数二',
            changeMsg:'',
            bool:true
         };
    }
    render() {
        return (
            <div>
                <p>{this.state.msg}</p>
                <p>{this.state.newMsg}</p>
                <button onClick={this.act}>改变新的msg的值</button>
                <button onClick={this.act01}>点击显示隐藏</button>
                {// 单分支
                    this.state.bool
                    &&
                    <div>
                        需要显示隐藏的内容!
                    </div>
                }
                {
                    // 双分支
                    this.state.bool
                    ?
                    <div>这是双分支需要显示隐藏的内容01!</div>
                    :
                    <div>这是双分支需要显示隐藏的内容02!</div>

                }
            </div>
        );
    }
    act = () => {
        this.setState((state, props) => {
            return {
                msg:state.newMsg,
                newMsg:state.msg
            }
        })
    }
    act01 = () => {
        this.setState((state, props) => {
            return {
                bool:!state.bool
            }
        })
    }
}

循环渲染

<div>
    <ul>
     {
        this.state.arr.map((el,index) => {
             return (
                   <li  key="index">
                       {el}
                    </li>
                )
         })
      }
     </ul>
  </div>

渲染富文本

content:'<div style="color:pink;">这是一个富文本内容!</div>'
<div dangerouslySetInnerHTML={{__html:this.state.content}}></div>

Fragment组件 做jsx根组件

react提供空的容器组件 一般用来作为jsx根组件

优势:实际渲染真实dom时 不会渲染任何标签(解决jsx嵌套过程 由于根元素的问题 导致多个无用的根标签的问题)

<Fragment>
                <div dangerouslySetInnerHTML={{__html:this.state.content}}></div>
            </Fragment>

componentDidMount

类似于mounted

react 事件

react结合原生事件 合成react事件 好处:事件函数可以绑定到组件实例上 调用setState修改数据 视图刷新

合成事件语法

on原生事件首字母大写

<button onClick={this.act}>改变新的msg的值</button>

绑定一定是一个函数 不能家括号调用

react事件函数的挂载方式

jsx行内新增箭头函数 充当事件函数

class Todo extends Component {
        state = { 
            msg:'11111111'
         };
    render() {
        return (
            <div>
                <div>
                    {this.state.msg}
                </div>
                <button onClick={
                () => {
                console.log(this)   //  指向的是实例
                this.setState((state,props) => {
                    return {
                        msg: '2222222'
                    }
                })
                }
            }>点击</button>
            </div>
        );
    }

}

注意:不推荐使用  jsx本质上是页面结构  函数里面要写js业务逻辑  这样写会造成结构和js业务逻辑混乱,不利于代码的维护

其他三种绑定事件的方式

事件函数挂载到原型上

注意:原型上面的方法不一定指向的就是实例,而是要看谁调用的 大部分情况下使用this.xxx调用的方法 这种情况下原型的方法指向实例Person

Person.prototype.xxx() 调用指向原型空间

事件函数调用并不是实例调用而是合成事件触发让它调用 this指向react变成undefined

解决方案:行内通过bind改变this的指向

<button onClick={
this.clickBtn.bind(this)
}></button>

缺点:

bind在render中调用,render在初始化和数据更新都会触发 导致多次调用bind 每一次都返回一个函数的副本浪费内存哦

将事件挂载到原型,constructor的this改变指向

image-20220622073403984.png

原理:constructor在实例上定义一个方法 值为原型上这个方法改变this的指向 意味着原型和实例上都有一个这个方法 实例上这个this指向实例 由于作用域关系 this调用这个方法时 一定拿到的是实例上面的方法

使用箭头函数 直接将方法定义到实例上

clickBtn = () => {

}

react事件对象

react合成事件的事件函数中 事件函数的第一个参数就是事件对象

e.stopPropagation()  阻止冒泡
e.preventDefault()  阻止默认事件
e.target  获取事件源

react事件传参

<button onClick={
(e) => {
	this.clickBtn(参数,e)
}
}></button>
clickBtn = (参数,e) => {

}

输入框

value和变量进行双单向绑定

import React, { Component } from "react";
class Todo extends Component {
        constructor () {
            super()
            this.state = { 
                msg:'1'
             };
        }
    render() {
        return (
            <div>
               <input value={this.state.msg} onChange={this.inputhandle}/>
               {this.state.msg}
            </div>
        );
    }
    inputhandle = (e) => {
        console.log(e.target.value)
        this.setState(
            {
                msg:e.target.value
            }
        )
    }

}

export default Todo;

class组件生命周期基础

生命周期基础

初始化阶段

constructor() static getDerivedStateFromProps() 相当于计算属性 render() 将jsx调用createElement渲染成虚拟dom componentDidMount() 初始化完成 可以在这里发送请求和获取dom

更新阶段

static getDerivedStateFromProps() shouldComponentUpdate() render() componentDidUpdate() 最新dom构建完成

注意:常用的是componentDidUpdate() 可以在这里获取最新dom,实际开发不建议在这里胡获取,会造成代码割裂,推荐在this.setState的回调函数中获取

卸载阶段

componentWillUnmount()

组件卸载时将组件实例从内存中移出,在组件初始化如果在全局上定义了一些特性的比如说定时器等,是不会共同移出的,组件卸载之前,手动在这个钩子中清除移出这些特性

生命周期高级部分

static getDerivedStateFromProps()

实现类似于vue中计算属性的功能 基于已有的props或者已有的state 计算得到一个新的state(初始化这些新的state是不在实例的state中的)

static getDerivedStateFromProps(props,state) props 定义初始state

return一个对象:

对象中的属性会自动添加到组件的state属性上

static getDerivedStateFromProps(props,state){
            return {
                changeProps:props.title,
                changeMsg:state.msg*100
            }
        }

挂载到了实例上的state中

image-20220622140805492.png

react更新机制

在react组件中 只要祖先组件更新了 不管有没有在后代中使用 后代组件默认都会更新

导致很大的性能浪费

解决:react将后代组件更新权力放给了开发者

开发者不处理:只要祖先组件更新了 不管有没有在后代中使用 后代组件默认都会更新

shouldComponentUpdate() 性能优化

方法:使用更新阶段生命周期钩子函数shouldComponentUpdate()

shouldComponentUpdate(nextProps,nextState) nextProps 更新之后的props nextState更新之后的nextState

shouldComponentUpdate(nextProps,nextState)返回值是一个布尔值:true 祖先组件更新 后代永远更新 false 永远不更新

更新的方案:祖先组件更新了 应该判断 内部的新旧props有没有发生变化

怎么办?

注意:不能直接比较props,因为引用类型比较的地址

1 先判断后代组件中有哪些props可能发生变化

2 直接比较这些props

3 值不一样 true 值一样 false

子组件继承 PureComponent 解决性能优化

原理:PureComponent会对props和state进行浅层比较 并且减少了跳过必要更新的可能性。

react 传递props 展开对象

obj={
a:1,b:2,c:3
}
<Todo {...obj}/>
等同于:
<Todo a={obj.a} b={obj.b} c={obj.c}/>

ref转发组件实例和dom对象

import React, { Component, createRef } from "react";
import TodoItem from './TodoItem'
class Todo extends Component {
        constructor () {
            super()
            this.toRef = createRef()
            this.btnRef = createRef()
        }
        componentDidMount () {
            console.log(this.toRef)
            console.log(this.btnRef)
            this.btnRef.current.style.background='red'
            console.log(this.toRef.current)
        }
    render() {
        return (
            <div>
              <TodoItem ref={this.toRef}/>
              <button ref={this.btnRef}>点击按钮</button>
            </div>
        );
    }


}

export default Todo;
import React,{ Component } from "react";
class TodoItem extends Component {
    constructor () {
        super()
        this.state={
            msg:'这是TodoItem的msg'
        }
    }
    render() {
        return (
            <div>这是TodoItem</div>
        );
    }
}

export default TodoItem;

image-20220622152231527.png

react组件通信

props

ref

兄弟组件通信

context 进行组件通信

目录中新建一个文件夹context 文件夹中新建一个index.js

import { createContext } from "react";
// 创建一个context对象
const context = createContext()
// 这个对象下有两个属性 
// Provider   react组件  value属性提供数据  提供的数据只能由他的后代组件  通过Comsumer来获取
// Comsumer  任意组件  用来获取Provider提供的数据的组件
const {Provider , Consumer} = context
export{
    context,
    Provider,
    Consumer
}

将父组件使用Provider包裹

const data={
  a:10,
  b:20,
  changeA(n){
    data.a = n
    console.log(data.a)
  }
}

<Provider value={data}>
  <Todo title="index传递的props值"/>
</Provider>

子组件中 根标签为Consumer包裹

render() {
        return (
            <Consumer>
                {
                    (data) => {
                        return (
                            <div>
                            <h1>A组件</h1>
                            {data.a}
                            <button onClick={
                                () => {
                                    data.changeA(5)
                                    this.forceUpdate()
                                }
                            }>点击修改A的值</button>
                        </div>
                        )
                    }
                }
            </Consumer>
        );
    }

context高级用法

import { createContext, Component, Fragment } from "react";
// 创建一个context对象
const context = createContext()
// 这个对象下有两个属性 
// Provider   react组件  value属性提供数据  提供的数据只能由他的后代组件  通过Comsumer来获取
// Comsumer  任意组件  用来获取Provider提供的数据的组件
const {Provider , Consumer} = context

// 定义封装class组件  封装provider
class ContextProvider extends Component{
    state={
        a:10,
        b:20
    }
    render(){
        return(
            <Fragment>
                <Provider value={{...this.state, changeA:this.changeA}}>
                    {this.props.children}
                </Provider>
            </Fragment>
        )
    }
    changeA = (n) => {
        this.setState({
            a:n
        })
    }
}


export{
    context,
    Provider,
    Consumer,
    ContextProvider
}
<ContextProvider>
          <Todo title="index传递的props值"/>
</ContextProvider>
render() {
        return (
            <Consumer>
                {
                    (data) => {
                        return (
                            <div>
                            <h1>A组件</h1>
                            {data.a}
                            <button onClick={
                                () => {
                                    data.changeA(5)
                                    // this.forceUpdate()
                                }
                            }>点击修改A的值</button>
                        </div>
                        )
                    }
                }
            </Consumer>
        );
    }

高阶组件

高阶函数

function fn(a){
return funciton(b){
  return a*b
}
}

函数柯里化 外层函数为高阶函数

高阶组件:

使用场景:修饰普通组件 给普通组件添加公共的视图结构 或 添加一些额外的props

实现步骤:本质上高阶组件是一个高阶函数 接受一个参数(就是要被修饰的普通组件) 函数返回一个组件

高阶组件命名:with+高阶组件功能名字

withTpl.js

import React, { Fragment } from 'react'

const withTpl = (Decorator) => {
    return (props) => {
        console.log(props,'222222222')
        return (
            <Fragment>
                <h1>公共头部分</h1>
                <Decorator {...props} c="dffgdfg">

                </Decorator>
                <h1>公共尾部分</h1>
            </Fragment>
        )
    }
}
export default withTpl

子组件

import React, { Component } from "react";
// import { Consumer } from "../context/index";
import withTpl from '../hoc/withTpl'
class TodoA extends Component {
    state = {  }
    render() {
        console.log(this.props,'1111111111111')
        return (
                <div>
                    这是TodoA的页面
                </div>
        );
    }
}

export default withTpl(TodoA);

父组件

<TodoA title="这是一个参数title"></TodoA>
<TodoB title="这是一个参数title"></TodoB>

函数式组件的学习

天生缺陷:没有内部状态 没有生命后期

在react16.8版本推荐react-hook钩子函数解决函数式组件缺陷

注意:hook函数一般use开头

所有hook只能在函数式组件中使用 且只能在函数式组件内部使用

useState

解决函数式组件没有内部状态问题

useState(10)定义初状态值为10

返回一个数组 [num,setState] 第一个是初始值 第二个是修改值得我方法

使用数组结构赋值 拿到初始值

修改值调用 第二个参数修改 视图自动刷新

注意:如果useState保存引用类型如 数组对象等 调用修改值得方法一定要传入新值(克隆),否则传递都是引用,比较认为没有变化 不会刷新视图

import React, { useState } from "react";

const TodoA = () => {
    const [num,setState] = useState(10)
        return(
            <div>
                这是TodoA
                <br />
                {num}
                <button onClick={
                    () => {
                        setState(num+10)
                    }
                }>点击修改值</button>
            </div>
        )
}

export default TodoA;

修改数组

import React, { useState } from "react";

const TodoA = () => {
    let arr = [1,2,3,4,5,6,7,8,9,10]
    const [arrTest,setState] = useState(arr)
        return(
            <div>
                这是TodoA
                <br />
                <ul>
                    {
                        arrTest.map((el,index) => {
                            return (
                                <li key={index}>
                                {el}
                            </li>
                            )
                        })
                    }
                </ul>
                <button onClick={
                    () => {
                        setState([
                            ...arrTest,arrTest.length+1
                        ])
                    }
                }>点击修改值</button>
            </div>
        )
}

export default TodoA;

useEffect解决生命周期钩子的问题

useEffect相当于componentDidMount和componentDidUpdate 初始化完成和更新完成都调用

import React, { useState,useEffect } from "react";
useEffect(()=>{
        console.log('useEffect调用了')   //  初始化的时候调用  每次修改数据的时候调用
    })

只在初始化完成触发

useEffect还有第二个参数 是数组(定义修改时触发的依赖变量)

例如:[a,b] 此时只有a,b任意一个变量发生改变 回调才触发相当于侦听器

useEffect(()=>{ },[a,b])

卸载前的钩子

useEffect(()=>{
//  这个回调只会在初始化完成触发
return ()=>{
// 返回的函数  只会在卸载前触发
}
},[])

注意:useState的方法 修改数据和dom更新时异步拿到新的数据

应该在修改数据后 定义一个 类似侦听器useEffect在这里拿到最新的数据和dom即可

useContext

解决函数式组件获取context对象Provider提供数据的问题

useContext(context)  //  返回该context对象Provider提供的value属性的值
import React, {useContext } from "react";
// import { Consumer } from "../context/index";
import { context } from '../context/index'

const TodoA = ()=>{
    console.log(useContext(context))
    const {a, changeA} = useContext(context)
    return(
        <div>
            {a}
            <br />
            <button onClick={
                ()=>{
                    changeA(1665450)
                }
            }>点击修改a的值</button>
        </div>
    )
}

export default TodoA;

useRef

在函数式组件中 获取子组件实例(class组件) 和 dom对象

如果子组件式函数式组件怎么处理? forwardRef

高阶组件将子函数式组件绑定容器传递到子组件内部 子组件函数的第二个参数再次转发给子函数式组件内部的某个dom对象

import React, {useContext, useRef, useEffect } from "react";

import { context } from '../context/index'
import TodoC from "./TodoC";

const TodoA = ()=>{
    const {a, changeA} = useContext(context)
    const todoRef = useRef()
    const btnRef = useRef()
    useEffect(()=>{
        console.log(todoRef.current)
        console.log(btnRef.current)
    },[])
    return(
        <div>
            {a}
            <br />
            <button onClick={
                ()=>{
                    changeA(1665450)
                }
            }>点击修改a的值</button>
            <hr />
            <TodoC ref={todoRef}/>
            <button ref={btnRef}>点击获取子组件的ref</button>
        </div>
    )
}

export default TodoA;
import React, {forwardRef} from "react";

const TodoC = forwardRef((props,btnRef) =>{

    return(
        <div>
            这是TodoC
            <button ref={btnRef}>点击获取</button>
        </div>
    )
})
export default TodoC

useMemo记忆函数

解决问题:在函数式组件中 祖先组件更新时 后代组件一定会重新调用使用(也会更新) 不同于class组件 这个子组件的调用时不可阻止的

性能问题:

useMemo 给定依赖 当useMemo发生改变时 useMemo回调出发 将子组件中业务函数在useMemo中触发 减少业务函数调用次数

回调初始化会触发一次   在更新时  依赖的props.b改变才会触发回调函数
useMemo(()=>{
fn()
console.log('触发了')
},[props.b])

return (
    <div>
      <h3>这是子组件</h3>
      {props.b}
    </div>
  );

react-router

react-router

react-router-dom

react-router-native

思想:一切都是通过组件来实现路由定义 不同于 vue路由config形式

根组件

作为整个应用根组件而存在 否则路由不生效

hashRouter

BroswerRouter

路由上有basename属性 ‘/Admin’ 意思就是访问别的路径的时候要带上/Admin

Route定义路由组件

是路由定义也是当前路由组件出口

Route定义路由,有哪些属性:

path 定义路由路径:

注意:路由path匹配默认是地址栏路由 地址是以Route的path开头即可匹配

当匹配成功一个组件 不会停止匹配 继续向下匹配其他路由(贪婪模式)

解决:只需要引入Switch组件包裹路由即可

<Switch>
     <Route path="/" exact component={Home}/>
     <Route path="/news" component={News}/>
     <Route path="/about" component={About}/>
</Switch>

component 定义路由组件

exact 精准匹配

Link

跳转路由

属性:

to:路由跳转的路由path

replace:跳转时覆盖当前历史记录

NavLink

跳转路由

具有Link所有的功能和属性

增加了导航高亮样式的处理

默认匹配导航 默认active类

activeClassName属性 修改高亮类

activeStyle 内联样式 定义高亮样式

exact 导航高亮样式 匹配 to 属性和地址一摸一样才能匹配

路由重定向和404

redirect 路由重定向

属性:

to:重定向地址

from:从哪儿来

exact:修饰from当前访问地址必须和from值一样时 开始重定向到to

404

路由提供了一个特殊的地址 * 匹配任意路由

<Route path="*" component={NotFound}>

注意:放置的位置在路由最后

嵌套路由

在父级路由组件中通过Route组件再定义子级路由

父级路由组件

<Route path="/news" component={News}/>

子级路由组件中

<Link to="/news/native">国内新闻</Link>
<Link to="/news/abroad">国外新闻</Link>
<Switch>
    <Route path="/news/native" component={NativeNews}/>
    <Route path="/news/abroad" component={AbroadNews}/>
    <Redirect to="/news/native" from="/news" exact/>
</Switch>

注意:

子级路由path一定要携带一级路由地址作为前缀

父级路由Route组件一定不能精准匹配加exact属性

编程式导航

利用js api 进行导航跳转

注意:不管是声明式的Link还是NavLink一级编程式跳转api 参数都是一样

在react路由中

react给所有的组件路由props灌入了三大对象

注意:只有Route的component属性对应的组件才是路由组件

props中三个对象:

history 保存操作路由api

listen 监听路由

go(n) 0 刷新 1 前进一步 -1 回退1步

push 跳转路由

replace 跳转路由 覆盖当前历史记录

const { history } = this.props;
history.go(0) // 刷新
history.push("/home")
history.replace('/about')

非路由组件获取操作路由props

react-router提供了一个高阶组件

with Router使用这个高阶组件修饰普通组件即可获取

class组件constructor中如何获取props

问题:class组件在constructor触发时,props还未绑定到实例上

class App extends Component {
  constructor(props){
    super(props);
    console.log(this.props,111);
  };
}

针对函数式组件 编程式导航

hook函数 useHistory在函数式组件中快速拿到路由的history对象

const About = (props) => {
  const history = useHistory();
  console.log(history,  222);
  return (
    <div>
      
    </div>
  );
}

路由传参

注意:只说跳转参数和获取

动态路由传参

定义动态路由

 <Route path="/news/:id" component={News}/>
history.push("/news/2")

参数获取 函数式组件和class组件都可以使用这个获取

this.props.match.params.参数名

函数式组件还可以使用useParams hook函数

import { useParams } from 'react-router-dom'
const params = useParams(); // {id: 2}

state传参

{
  pathname: '/home',
  state: {
    a: 10,
    b: 20
  }
}

获取参数

this.props.location.state.xxx

函数式组件还可以使用 useLocation hook函数

search传参

在地址栏携带参数传递

跳转时path后携带search参数

/about?a=10&b=20

获取参数

this.props.location.search // 注意需要手动解析成对象才可以使用

qs插件

qs.stringify() qs将对象转成query格式

qs.parse() qs将query转成对象

Route组件的render属性

render值是一个函数 return jsx

当 当前路由匹配时 渲染render方法返回的jsx

<Route path="/news" render={(routeProps) => {
            return <News {...routeProps}/>
}}/>

注意:由于使用render 目标组件不再是路由组件 是直接渲染的 render函数参数就是props三大对象 通过props传递的形式 传递给 目标路由组件

登录鉴权

使用redirect组件鉴权

原理:在某个需要登录的路由组件内部判断是否登录 没有登录则渲染 redirect组件进行重定向到登录页即可

const About = (props) => {

  return (
    <div>
      {
        !isLogin()
        &&
        <Redirect to="/login"/>
      }
      <h2>关于我们</h2>
    </div>
  );
}

利用render做登录鉴权

<Route path="/about" render={(routeProps) => {
              if(isLogin()){
                // 登录了
                return <About {...routeProps}/>
              }else{
                // 没有登录
                return <Redirect to={
                  {
                    pathname: '/login',
                    state: {
                      from: '/about'
                    }
                  }
                }/>
              }
          }}/>

Redux

是JavaScript状态管理容器 提供可预测的状态管理

redux核心概念

单一状态树

state是只读的(只能由render来修改)

reducer必须是纯函数 什么是纯函数:函数返回结果一定依赖他的参数 且中间不能有副作用

基础使用

安装

npm i redux -S

创建仓库

引入创建仓库方法

import {  legacy_createStore as createStore } from 'redux'
import { cloneDeep } from "lodash";

初始state

const defaultState = {
  num: 10
}

定义reducer

const reducer = (state=defaultState, action) => {
  // 对于state做深克隆
  const newState = cloneDeep(state);
  return newState
}

创建仓库

const store = createStore(reducer);

在组件中操作redux:

在组件获取state store.getState();

组件中提交action

/* 
      action是对象
      固定属性
        type 代表action 类型(action是干啥)
    */
    store.dispatch({
      type: 'ADD_NUM',
      data: 5
    })

组件中调用store.subscribe方法监听store中state的变化

// subscribe观察者,观察的就是 仓库中state的变化,变化后观察者回调触发
    store.subscribe(() => {
      this.setState({
        ...store.getState()
      })
    })

问题:

action每一次定义一个对象 没有任何复用性

action type属性是直接写的字符串(字符串写错了,reducer数据无法修改 且不报错)

单独抽离action使用函数封装 函数返回action 称这个函数为actionCreator

// actionCreators.js
import {ADD_NUM, REDUCE_NUM} from "./actionTypes"

const add_num = (data) => {
  return {
    type: ADD_NUM,
    data
  }
}
const reduce_num = (data) => {
  return {
    type: REDUCE_NUM,
    data
  }
}

解决type 单独在外部定义 常量 保存action的type reducer和action中都使用长量代表type

// actionTypes.js
export const ADD_NUM = 'ADD_NUM'
export const REDUCE_NUM = 'REDUCE_NUM'

reducer中判断type使用常量

import {ADD_NUM, REDUCE_NUM} from "./actionTypes";
const reducer = (state=defaultState, action) => {
  const newState = cloneDeep(state);
  switch (action.type) {
    case ADD_NUM:
      newState.num += action.data
      break;
    case REDUCE_NUM:
      newState.num -= action.data
      break;
    default:
      break;
  }
  return newState
}

组件中提交action

import {add_num, reduce_num} from '../store/actionCreators';

store.dispatch(add_num(5))

异步解决问题

store中某些state需要请求接口拿到数据 需要发送异步ajax

actionCreator中默认不能发送异步请求

解决:使用redux异步插件

redux-thunk

redux-promise

redux-saga 基于 es6 generator

redux-thunk插件

import { legacy_createStore as createStore,  applyMiddleware } from 'redux'

import 插件1
import 插件2
const store = createStore(reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
    applyMiddleware(插件1,插件2))

如何做异步处理:

增加了actionCreator的功能 原本必须函数return一个对象action

使用redux-thunk actionCreator可以return一个函数 内层内部可以发送异步请求 且内层函数里面有一个参数dispatch,就是store下的dispatch

具体实现:

import { legacy_createStore as createStore,  applyMiddleware } from 'redux'
import { cloneDeep }  from 'lodash'
import { ADD_NUM, INIT_DATA } from '../store/actionType'
import  thunk  from 'redux-thunk'
const defaultState={
    num:10,
    initNum:[]
}

const reducer=(state=defaultState, action)=>{
    const newState = cloneDeep(state)
    if(action.type === ADD_NUM){
        newState.num += action.data
    }else if(action.type === INIT_DATA){
        newState.initNum = action.data
    }
    
    return newState

}

const store = createStore(reducer,   //  只能有两个参数
    // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
    applyMiddleware(thunk))

export default store
export const INIT_DATA = 'INIT_DATA'
import axios from 'axios'
import { INIT_DATA } from './actionType'

const fetch = (params)=>{
    return dispatch => {
        axios.get('https://api.it120.cc/conner/shop/goods/category/all',{params}).then(res=>{
            console.log(res)
            if(res.data.code === 0){
                dispatch({
                    type:INIT_DATA,
                    data:res.data.data
                })
            }
        })
    }
}

export {
    fetch
}
componentDidMount () {
        store.dispatch(fetch({
            page:1,
            pageSize:10
        }))
    }

Redux-devtools

Chrome安装插件以后

在createStore第二个参数加上window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()

const store = createStore(reducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())

\