react基础学习二

86 阅读11分钟

说明:以下内容都是基于18版本以前的,如有不同内容不符请查看对应版本内容


安装

1.什么是脚手架?

脚手架是一种能快速帮助我们生成项目结构和依赖的工具

  • 每个项目完成的效果不同,但是它们的基本工程化结构是相似的
  • 既然相似,就没有必要每次都从零开始搭建,完全可以使用一些工具,帮助我们生成基本的项目模板
  • 那么这个帮助我们生成项目模板的工具我们就称之为'脚手架'

2.什么是create-react-app?

create-react-app就是React的脚手架工具, 它可以快速的帮我们生成一套利用webpack管理React的项目模板

3.如何安装和使用create-react-app?

npm install -g create-react-app
create-react-app 项目名称
cd 项目名称
npm start
  • 注意点: 如果我们是通过create-react-app来创建React项目
    • 那么在指定项目名称的时候, 项目的名称只能是英文, 并且只能是小写字母
    • 如果出现了多个单词, 那么我们需要通过_-来连接. myName->my_name->my-name
  • 注意点: 第一次运行项目的时候大概率会出现一个错误, 会出现本地webpack的版本和项目依赖的webpack版本不同的错误
    • 如果遇到了这个错误, 我们就需要先通过 npm uninstall webapck 卸载掉本地的webpack
    • 再通过 npm install -g webpack@xx.xx.xx安装和项目相同版本的webpack即可

create-react-app

4.暴露webapck配置

npm run eject

项目结构解读

1.通过create-react-app生成的项目结构解读

项目名称
├── README.md           // 说明文档
├── node_modules        // 依赖包
├── package.json        // 对应用程序的描述
├── .gitignore          // 不通过git管理的文件描述
├── public
│   ├── favicon.ico    // 应用程序收藏图标
│   ├── index.html     // 应用程序入口
│   ├── logo192.png    // manifest中PWA使用的图片
│   ├── logo512.png    // manifest中PWA使用的图片
│   ├── manifest.json  // PWA相关配置
│   └── robots.txt     // 搜索引擎爬虫权限描述文件
└── src
    ├── App.css         // 组件相关样式
    ├── App.js          // 组件相关代码
    ├── App.test.js     // 组件测试文件
    ├── index.css       // 全局样式
    ├── index.js        // 全局入口文件
    ├── logo.svg        // React图标
    └── serviceWorker.js// 注册PWA相关代码

2.什么是PWA?

小程序老祖宗

PWA

image.png

组件定义、父子通讯

1.如何在子组件设置参数默认值?

  • 通过defaultProps

react官网

2.如何在子组件中校验参数类型?

  • 通过propTypes
npm install prop-types

react: prop-types

3.组件的定义

  • 函数组件
  • 类组件

image.png

函数组件与 class 组件

跨组件事件通讯

  • 通讯: 父子、子父、爷孙、兄弟

层层传递,比较复杂

  • context上下文来传递数据
    • 调用创建上下文的方法,只要调用了创建上下文的方法,这个方法就会返回两个容器: 生产者容器组件(Provider)、消费者容器组件(Consumer)
    • 只要拿到了这两个容器组件, 那么我们就可以通过这两个容器组件从祖先传递数据给所有的后代了
    • 首先在祖先组件中利用 '生产者容器组件' 包裹后代组件
    • 然后在后代组件中利用 '消费者容器组件' 获取祖先组件传递过来的数据即可

context只能实现从上往下传递

  • events
    • 安装events库,npm install events
    • 创建EventEmitter对象: eventBus对象
    • 监听事件:eventBus.addListener('事件名称', 监听函数)
    • 移除事件: eventBus.removeListener('事件名称', 监听函数)
    • 发出事件:eventBus.emit('事件名称', 参数列表)

示例

props和state区别:

  • props和state都是用来存储数据的
    • props存储的是父组件传递归来的数据
    • state存储的是自己的数据
    • props只读的
    • state可读可写

props只读

State & 生命周期

setState是同步的还是异步的

从react 18开始, 使用了createRoot创建应用后, 所有的更新都会自动进行批处理(也就是异步合并).使用render的应用会保持之前的行为. 如果想保持同步更新行为, 可以使用ReactDOM.flushSync().

下面是18版本之前的

  • 在组件生命周期或React合成事件中,setState是异步;
  • 在setTimeout或者原生dom事件中,setState是同步;

说明

  • 1.setState是同步的还是异步的?
    • 默认情况下setState是异步的
  • 2.为什么setState是异步的?
    • 主要是为了优化性能
  • 3.如何拿到更新之后的数据?
    • setState方法其实可以接收两个参数
    • 通过setState方法的第二个参数, 通过回调函数拿到
  • 4.setState一定是异步的吗?
    • 不一定: 在定时器中, 在原生事件中

书写方式

this.setState({
    age: 222
});
this.setState({
    age: 111
}, ()=>{
    console.log('回到函数中', this.state.age);
});
this.setState((preState, props) => {
    return { num: preState.num + 1 }
})

setState

setState是如何给state赋值的

通过Object.assign()

state合并现象

  • 因为setState会收集一段时间内所有的修改再更新界面,所以就出现了State合并现象 state合并现象

组件生命周期

组件生命周期图谱

image.png 16之前生命周期.jpg

1.什么是生命周期?

事物从生到死的过程, 我们称之为生命周期

2.什么是生命周期方法?

事物在从生到死过程中, 在特定时间节点调用的方法, 我们称之为生命周期方法

3.React组件生命方法?

组件从生到死的过程, 在特定的时间节点调用的方法, 我们称之为组件的生命周期方法

  • constructor函数: 组件被创建的时候, 就会调用
  • render函数: 渲染组件的时候, 就会调用
  • componentDidMount函数:组件已经挂载到DOM上时,就会回调
  • componentDidUpdate函数:组件已经发生了更新时,就会回调
  • componentWillUnmount函数:组件即将被移除时,就会回调

作用

  • constructor
    • 通过props接收父组件传递过来的数据
    • 通过this.state初始化内部的数据
    • 通过bind为事件绑定实例(this)
  • render
    • 返回组件的网页结构
  • componentDidMount
    • 依赖于DOM的操作可以在这里进行
    • 在此处发送网络请求最好的地方(官方建议)
    • 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)
  • componenetDidUpdate
    • 在此对更新之后的组件进行操作
  • componentWillUnmount
    • 在此方法中执行必要的清理操作
    • 例如:清除timer,取消网络请求或清除在componentDidMount()中创建的订阅等

diff算法

概念理解

image.png

/*
1.React渲染流程
- 执行render方法
<div>
    <div><p>我是段落</p></div>
    <div><span>我是span</span></div>
</div>
- 将JSX转换成createElement
React.createElement("div", null,
    React.createElement("div", null,
        React.createElement("p", null, "我是段落")),
    React.createElement("div", null,
        React.createElement("span", null, "我是span"))
);
- 执行createElement创建虚拟DOM, 得到虚拟DOM树
{
 targetName: 'div',
 children:[
    {
     targetName: 'div',
     children:[
        {
         targetName: 'p'
        }
     ]
    },
    {
     targetName: 'div',
     children:[
        {
         targetName: 'span'
        }
     ]
    }
 ]
}
- 根据虚拟DOM树在界面上生成真实DOM
*/

/*
2.React更新流程
- props/state发生改变
- render方法重新执行
- 将JSX转换成createElement·
- 利用createElement重新生成新的虚拟DOM树
- 新旧虚拟DOM通过'diff算法'进行比较
- 每发现一个不同就生成一个mutation
- 根据mutation更新真实DOM

3.React-Diff算法
- 只会比较同层元素
- 只会比较同位置元素(默认)
- 在比较过程中:
    + 同类型元素做修改
    + 不同类型元素重新创建
* */

Diffing 算法

列表性能优化

1.列表渲染优化 由于diff算法在比较的时候默认情况下只会进行同层同位置的比较 所以在渲染列表时可能会存在性能问题

2.如何让diff算法递归比较同层所有元素? 给列表元素添加key,告诉React除了和同层同位置比, 还需要和同层其它位置比 keys

key注意点:

如果列表中出现相同的key, 所以我们必须保证列表中key的取值唯一

组件性能优化

1.嵌套组件的render调用 默认情况下, 只要父组件render被调用, 那么所有后代组件的render也会被调用

2.当前存在的问题 如果我们只修改了父组件的数据, 并没有修改子组件的数据, 并且子组件中也没有用到父组件中的数据 那么子组件还是会重新渲染, 子组件的render方法还是会重新执行, 这样就带来了性能问题

解决(类组件)

shouldComponentupdate

shouldcomponentupdate

shouldComponentUpdate(nextProps, nextState, nextContext) {
    if (this.state.age !== nextState.age) {
        return true // 数据改变则更新
    }
    return false // 数据不改变不更新
}

问题:

  • 1.shouldComponentUpdate存在的问题 所有需要优化子组件都需要实现这个方法, 但这个方法并没有技术含量 image.png
  • 2.解决方法 让组件继承于PureComponent, 让React自动帮我们实现

PureComponent

import React from "react"
class Home extends React.PureComponent {
    constructor () {
        super()
        this.state = { age: 18 }
    }
    // shouldComponentUpdate(nextProps, nextState, nextContext) {
    //     if (this.state.age !== nextState.age) {
    //         return true
    //     }
    //     return false
    // }
    render() {
        console.log('Home-render')
        return (
            <div>
                Home
            </div>
        )
    }
}
class App extends React.Component {
    constructor () {
        super()
        this.state = {
            num: 0
        }
    }
    render() {
        console.log('App-render')
        return (
            <div>
                <Home />
                <div>{this.state.num}</div>
                <button onClick={() => this.showHome()}>App按钮</button>
            </div>
        )
    }
    showHome() {
        this.setState({
            num: this.state.num + 1
        })
    }
}
export default App

函数式组件

  • 没有继承关系
  • 没有生命周期方法

React.memo()高阶函数

React.memo()会返回一个优化后的组件

const PurHome = React.memo(function () {
    console.log('PurHome-render被调用')
    return (
        <div>
            PurHome
        </div>
    )
})

state注意点

永远不要直接修改state中的数据,如果要修改state中的数据,必须通过setState传递一个新的值

image.png

直接报问题,并且点击修改没有任何反应

  • 以前的版本
    • 可以改,但是在shouldComponentUpdate、React.PurComponent下不会触发更新
import React from 'react';
/*
1.state注意点
永远不要直接修改state中的数据
如果要修改state中的数据, 必须通过setState传递一个新的值
 */
class App extends React.PureComponent{
    constructor(props){
        super(props);
        this.state = {
            age : 0
        }
    }
    render(){
        console.log('App-render被调用');
        return (
            <div>
                <p>{this.state.age}</p>
                <button onClick={()=>{this.btnClick()}}>APP按钮</button>
            </div>
        )
    }
    /*
    shouldComponentUpdate(nextProps, nextState, nextContext) {
        console.log(this.state.age, nextState.age); // 0 1 //1 1
        if(this.state.age !== nextState.age){
            return true;
        }
        return false;
    }
    */
    btnClick(){
        // 以下两种写法区别:
        // 上面一种方式是设置了一个新的对象
        // 下面这种方式是设置了以前的对象

        // this.setState({
        //     age:this.state.age + 1
        // })
        this.state.age = this.state.age + 1;
        this.setState(this.state);
    }
}
export default App;

react获取元素

1.React中获取元素的方式

  • 传统方法 操作DOM
  • 字符串 ref='xxx'
  • 对象 React.createRef
  • 回调函数
import React from "react"

class App extends React.Component {
    constructor () {
        super()
        this.oPRef = React.createRef()
        this.oDRef = null
    }
    render() {
        console.log('App-render')
        return (
            <div>
                {/* <div id={'box'}>p</div> */}
                {/* <div ref={'box'}>p</div> */}
                {/* <div ref={this.oPRef}>p</div> */}
                <div ref={(arg) => { this.oDRef = arg }}>p</div>
                <button onClick={() => this.showHome()}>App按钮</button>
            </div>
        )
    }
    showHome() {
        console.log('点击了')
        // 第一种获取方式: 传统方式(在react中不推荐)
        // const box = document.getElementById('box')
        // 第二种获取方式: 通过ref='字符串'
        // const box = this.refs.box // refs被弃用
        // 第三种方式:通过React.createRef()创建一个对象,然后将这个对象传递给ref (推荐)
        // const box = this.oPRef.current
        // 第四种获取方式:通过传递一个回调函数,然后保存回调函数参数的方式(推荐)
        const box = this.oDRef
        box.innerHTML = 'div'
    }
}
export default App

createRef

注意点

  • 获取原生元素,拿到的是元素本身
  • 获取类组件元素,拿到的是组件实例对象
  • 获取函数组件元素,拿不到任何内容
import React from "react"
class Home extends React.PureComponent {
    render() {
        return (
            <div>Home</div>
        )
    }
}
function About() {
    return (
        <div>About</div>
    )
}
class App extends React.Component {
    constructor () {
        super()
        this.oPRef = React.createRef()
        this.oDRef = null
    }
    render() {
        console.log('App-render')
        return (
            <div>
                {/* <div id={'box'}>p</div> */}
                {/* <div ref={'box'}>p</div> */}
                {/* <div ref={this.oPRef}>p</div> */}
                {/* <div ref={(arg) => { this.oDRef = arg }}>p</div> */}
                {/* <Home ref={this.oPRef} /> */}
                <About ref={this.oPRef} />
                <button onClick={() => this.showHome()}>App按钮</button>
            </div>
        )
    }
    showHome() {
        console.log('点击了')
        // const box = document.getElementById('box')

        // const box = this.refs.box // refs被弃用
        // const box = this.oPRef.current
        // const box = this.oDRef
        // box.innerHTML = 'div'
        const Home = this.oPRef.current // 类组件:组件实例;函数组件:null
        console.log('Home', Home)
    }
}
export default App

image.png

Ref转发

1.直接通过ref获取不到函数式组件 2.如果要获取的不是函数式组件本身,而是想获取函数式组件中的某个元素,可以使用Ref转发来获取

React.forwardRef

import React from "react"
class Home extends React.PureComponent {
    render() {
        return (
            <div>Home</div>
        )
    }
}
// function About() {
//     return (
//         <div>About</div>
//     )
// }
const About = React.forwardRef(function (props, myRef) { // myRef === my.oPRef 外界创建的ref传给函数式组件
    return (
        <div>
            <p>About</p>
            <p ref={myRef}>About-myRef</p>
        </div>
    )
})
class App extends React.Component {
    constructor () {
        super()
        this.oPRef = React.createRef()
        this.oDRef = null
    }
    render() {
        console.log('App-render')
        return (
            <div>
                {/* <div id={'box'}>p</div> */}
                {/* <div ref={'box'}>p</div> */}
                {/* <div ref={this.oPRef}>p</div> */}
                {/* <div ref={(arg) => { this.oDRef = arg }}>p</div> */}
                {/* <Home ref={this.oPRef} /> */}
                <About ref={this.oPRef} />
                <button onClick={() => this.showHome()}>App按钮</button>
            </div>
        )
    }
    showHome() {
        console.log('点击了')
        // const box = document.getElementById('box')

        // const box = this.refs.box // refs被弃用
        // const box = this.oPRef.current
        // const box = this.oDRef
        // box.innerHTML = 'div'
        const About = this.oPRef.current // 类组件:组件实例;函数组件:null
        console.log('About', About)
    }
}
export default App

受控组件

值受到react控制的元素。元素的值来源、去向都是state

react-受控组件 示例

非受控组件

值不受到react控制的元素。元素的值【来源】、【去向】一者或都不是state

react-非受控组件 示例

高阶组件

react-高阶组件

  • 高阶组件的参数为组件,返回值为新组建的函数
class Home extends React.PureComponent {
    render() {
        return (
            <div>Home</div>
        )
    }
}
function enhancedComponent(WrappedComponent) {
    class AdvComponent extends React.PureComponent {
        render() {
            return (
                <WrappedComponent />
            )
        }
    }
    return AdvComponent
}
const AdvComponent = enhancedComponent(Home)
class App extends React.PureComponent {
    render() {
        return (
            <AdvComponent></AdvComponent>
        )
    }
}

应用场景

  • 代码复用
  • 增强props
  • 抽离state
  • 生命周期拦截
  • 权限控制 示例

Portals

默认情况下,所有的组件都是渲染到root元素中。Portal可以让组件渲染到其他元素中 Portals

createPortal

  • 参数一:需要渲染的内容
  • 参数二:渲染到什么地方
import React from "react"
import ReactDOM from 'react-dom'
class Modal extends React.PureComponent {
    render() {
        // this.props.children: 可以获取到当前组件所有的子元素或者子组件
        return ReactDOM.createPortal(this.props.children, document.getElementById('other'))
    }
}
class App extends React.PureComponent {
    render() {
        return (
            <div id={'app'}>
                <Modal>
                    <div id={'modal'}>Modal</div>
                </Modal>
            </div>
        )
    }
}
export default App

Fragment

  • 由于React规定, 组件中只能有一个根元素 所以每次编写组件的时候, 我们都需要在最外层包裹一个冗余的标签
  • 如果不想渲染这个冗余的标签, 那么就可以使用Fragment来代替

如果需要给Fragment添加key, 那么就不能使用语法糖

示例

StrictMode

StrictMode 1.什么是StrictMode? 作用: 开启严格模式, 检查后代组件中是否存在潜在问题 注意点:

  • 和Fragment一样, 不会渲染出任何UI元素
  • 仅在'开发模式'下有效

2.StrictMode检查什么?

  • 检查过时或废弃的属性/方法/...
  • 检查意外的副作用
  • 这个组件的constructor会被调用两次
  • 检查这里写的一些逻辑代码被调用多次时,是否会产生一些副作用
ReactDOM.render(
    <React.StrictMode>
        <App/>
    </React.StrictMode>
    , document.getElementById('root'));

学习笔记记录