前端面试基础题:React JSX、高级组件和路由…

2023-07-0309:19:53WEB前端开发Comments905 views字数 12548阅读模式

✨React介绍

如果从 MVC 的角度来看, React 仅仅是视图层(V),也就是只负责视图的渲染,而并非提供了完整的 M 和 C 的功能。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

特点:声明式、基于组件、学习一次随处使用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

脚手架的使用:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 创建项目:npx/npm init/yarn create-react-app app-name
  2. 在脚手架中使用:导入reactreact-dom两个包
import React from 'react'
import ReactDOM from 'react-dom'

// 调用 React.createElement(创建React元素名称, 该React元素的属性, 该React元素的子节点) 方法创建react元素
// 调用 ReactDOM.render(要渲染的React元素, 用于指定渲染到页面中位置的DOM对象) 方法渲染react元素到页面中
const title = React.createElement('h1', null, 'Hello React');
ReactDOM.render(title, document.getElementById('root'));

React的设计思想:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 组件化:每个组件都符合开放-封闭原则,封闭是针对渲染工作流来说的,指的是组件内部的状态都由自身维护,只处理内部的渲染逻辑。开放是针对组件通信来说的,指的是不同组件可以通过props(单项数据流)进行数据交互。
  2. 数据驱动视图:如果要渲染界面,不应该直接操作DOM,而是通过修改数据(state或prop),数据驱动视图更新。
  3. 虚拟DOM:虚拟DOM是对真实DOM的映射,React通过新旧虚拟DOM对比,得到需要更新的部分,实现数据的增量更新。

JSX

JSX 是 JavaScript XML 的简写,表示在 JavaScript 代码中写 XML(HTML) 格式的代码。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

JSX仅仅是createElement()方法的语法糖(简化语法),JSX语法被@babel/preset-react插件编译为createElement()方法。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

JSX和JS的区别:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. JS可以被打包工具直接编译,不需要额外转换,jsx需要通过babel编译,它是React.createElement的语法糖,使用jsx等价于React.createElement
  2. jsx是js的语法扩展,允许在html中写JS;JS是原生写法,需要通过script标签引入。

基本使用步骤:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

// 使用JSX语法,创建react元素
const title = (<h1>Hello</h1>);
// 使用ReactDOM.render()方法渲染react元素到页面中
ReactDOM.render(title, root);
  • React元素的属性名使用驼峰命名法
  • 特殊属性名:class → className(给HTML元素添加类名),for → htmlFor(label标签),tabindex → tabIndex
  • 没有子节点的React元素可以用/>结束
  • 推荐:使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱
  1. JSX中使用JS表达式
const name = 'Jack';
const dv = (<div>你好我叫{name}</div>);

语法中是单大括号,不是双大括号;单大括号中可以使用任意的JavaScript表达式,不能出现语句(if / for等);JS中的对象是一个例外,一般只会出现在style属性中。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

2. JSX的条件渲染文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

根据条件渲染特定的 JSX 结构,可以使用if/else或三元运算符或逻辑与运算符来实现 。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

const loadData = () => {
    if (isLoading) {
        return (<div>数据加载中请稍后...</div>);
    }
    return (<div>数据加载完成此处显示加载后的数据</div>);
}

const dv = (<div>{loadData()}</div>)

3. JSX的列表渲染文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

如果要渲染一组数据,应该使用数组的map()方法 ,渲染列表时应该添加key属性,key属性的值要保证唯一。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

const songs = {
    { id: 1, name: "痴心绝对" },
    { id: 2, name: "像我这样的人" },
    { id: 3, name: "南山南" }
}

const list = (
    <ul>
        { songs.map(item => <li key={item.id}>{item.name}</li>) }
    </ul>
)

4. JSX的样式处理文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

行内样式——style文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

<h1 style={ { color: 'red', backgrondColor: 'skyblue' } }>
    JSX的样式处理
</h1>

类名——className文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

<h1 className="title">
    JSX的样式处理
</h1>

创建React组件

类组件和函数组件的区别:类组件是有状态的,即具有内部私有数据(state),函数组件是无状态的。函数组件没有生命周期方法,只有类组件有。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 使用函数创建组件
  • 使用 JS 的函数(或箭头函数)创建的组件
  • 函数名称必须以大写字母开头
  • 函数组件必须有返回值,表示该组件的结构
  • 如果返回值为 null,表示不渲染任何内容
  • 渲染函数组件: 用函数名作为组件标签名
  • 组件标签可以是单标签也可以是双标签
// 创建
function Hello () {
    return <div>这是我的第一个函数组件</div>
]
// 渲染
ReactDOM.render(<Hello/>, root);

2. 使用类创建组件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 使用ES6的class创建的组件
  • 类名称也必须以大写字母开头
  • 类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性
  • 类组件必须提供render()方法
  • render()方法必须有返回值,表示该组件的结构
class Hello extends React.Component {
    render() {
        return <div>Hello, Class Component!</div>
    }
}
ReactDOM.render(<Hello/>, root)

3. 将组件抽离为独立JS文件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

// Hello.js
import React from 'react'
class Hello extends React.Component {
    render () {
        return <div>Hello Class Component!</div>;
    }
}
export default Hello; // 导出Hello组件
 
// index.js
import Hello from './Hello.js'
ReactDOM.render(<Hello/>, root);

有状态组件和无状态组件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 函数组件又叫做无状态组件,类组件又叫做有状态组件
  • 函数组件没有自己的状态, 只负责数据展示(静)
  • 类组件有自己的状态, 负责更新 UI,让页面“动” 起来
  • 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
  • state 的值是对象,表示一个组件中可以有多个数据
  • 通过this.state获取状态,使用this.setState({要修改的数据})修改状态
  • setState是一个异步方法,但是在setTimeout/setInterval等定时器里逃脱了React对它的掌控,变成了同步方法
  • setState方法第二个参数是一个可选的回调函数,可以获取最新的state值。回调函数会在组件更新完成之后执行,等价于在 componentDidUpdate 生命周期内执行

为什么不能直接修改state,而是必须调用setState文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

state值的改变,目的是页面的更新,希望React使用最新的state来渲染页面,但是直接赋值的方式并不能让React监听到state的变化。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

class Hello extends React.Component {
    constructor () {
        super () {
            // 初始化state
            this.state = {
                count: 0
            }
        }
    }
    // 简化语法
    // state = {
    //    count: 0
    // }
    render() {
        return (
            <div>有状态组件: {this.state.count}</div>
        )
    }
}

// 修改状态
// 正确
this.setState({
    count: this.state.count + 1
})
// 错误
this.state.count += 1

✨ React事件处理

事件绑定文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • React事件绑定语法与DOM事件语法相似
  • 语法: on+事件名称={事件处理程序},比如:onClick={() => {}}
  • 注意:React 事件采用驼峰命名法,比如: onMouseEnter、 onFocus
function App() {
    function handleClick () {
        console.log('单击事件触发')
    }
    return <button onClick={this.handleClick}>点我</button>
}

class App extends React.Component {
    handleClick () {
        console.log('单击事件触发')
    }
    render () {
        return <button onClick={this.handleClick}>点我</button>
    }
}

事件对象文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 可以通过事件处理程序的参数获取到事件对象
  • React中的事件对象叫做:合成事件(对象)
  • 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
function handleClick(e) {
    e.preventDefault()
    console.log('事件对象', e)
}

<a onClick={handleClick}>点我不会跳转页面</a>

事件绑定的this指向文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

事件处理程序中this的值为undefined文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

onIncrement () {
    this.setState({  // TypeError: Cannot read property 'setState' of undefined
        count: this.state.count + 1
    })
}

希望:this指向组件实例(render方法中的this指向组件实例)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 箭头函数

利用箭头函数自身不绑定this的特点,render()方法中的this为组件实例,可以获取到setState()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

class Hello extends React.Component {
    onIncrement () {
        this.setState({ count: this.state.count + 1 })
    }
    render () {
        return (<button onClick={ () => this.onIncrement() }></button>)
    }
}

2. Function.prototype.bind()文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

class Hello extends React.Component {
    constructor () {
        super() 
        this.onIncrement = this.onIncrement.bind(this)
    }
    // ...省略onIncrement
    render () {
        return (<button onclick={this.onIncrement}></button>)
    }
}

3. class的实例方法文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

利用箭头函数形式的class实例方法 ,注意:该语法是实验性语法,但是,由于babel的存在可以直接使用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

class Hello extends React.Component{ 
    onIncrement = () => {
        this.setState({...})
    }
    render () {
        return (<button onclick={this.onIncrement}></button>)
    }
}

表单处理

  1. 受控组件

其值受到React控制的表单元素。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
  • 给表单元素绑定change事件,将 表单元素的值设置为state的值(控制表单元素值的变化)
state = { txt: '' }

<input type="text" value={this.state.txt} onChange={ e => this.setState({ txt: e.target.value }) } />

2. 非受控组件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

借助于ref,使用原生DOM方式来获取表单元素值。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 调用React.createRef()方法创建一个对象
  • 将创建好的ref对象添加到文本框中
  • 通过ref对象获取到文本框的值
// 1.
constructor () {
    super()
    this.txtRef = React.createRef()
}
// 2.
<input type="text" ref={this.txtRef} />
// 3.
console.log(this.txtRef.current.value)

✨组件通讯

  1. 父组件传递数据给子组件
  • 父组件提供要传递的state数据
  • 给子组件标签添加属性,值为state中的数据
  • 子组件中通过props接收父组件中传递的数据
// 父组件
class Parent extends React.Component {
    state = { lastName: '王' }
    render () {
        return (
            <div>传递给子组件<Child name={this.state.lastName} /></div>
        )
    }
}

// 子组件
function Child (props) {
    return (<div>子组件接收到的数据{props.name}</div>)
}

2. 子组件传递数据给父组件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 父组件提供一个回调函数(用于接收数据)
  • 该函数作为属性的值,传递给子组件
// 父组件
class Parent extends React.Component {
    getChildMsg = (msg) => {
        console.log('接收到子组件的数据:', msg);
    }
    render () {
        return (
            <div>
                子组件<Child getMsg={this.getChildMsg} />
            </div>
        )
    }
}

// 子组件
class Child extends React.Component {
    state = { childMsg: 'React' }
    handleClick = () => {
        this.props.getMsg(this.state.ChildMsg)
    }
    render () {
        rerturn (<button onclick={ this.handleClick }>点我</button>);
    }
}

3. 兄弟组件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
  • 公共父组件职责: 1. 提供共享状态 2. 提供操作共享状态的方法
  • 要通讯的子组件只需通过props接收状态或操作状态的方法
// 父组件
class Counter extends React.Component {
    // 提供共享状态
    state = { count: 0 }
    // 提供修改状态的方法
    onIncrement = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        return (
            <div>
                <Child1 count={ this.state.count } />
                <Child2 onIncrement={ this.onIncrement } />
            </div>
        )
    }
}

// 子组件
const Child1 = props => {
    return <h1>计数器{ props.count }</h1>
};
const Child2 = props => {
    return <button onClick={ () => props.onIncrement() }>+1</button>
}

✨Context

用于跨组件传递数据。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 调用React.createContext()创建Provider(提供数据) 和 Consumer(消费数据)两个组件。
  2. 使用Provider组件作为父节点。
  3. 设置value属性,表示要传递的数据。
  4. 调用Consumer组件接收数据。
const { Provider, Consumer } = React.createContext()
export { Provider, Consumer }


<Provider  value="pink">
    <div className="App">
    	<Child1 />
    </div>
</Provider>

    
<Consumer>
    {data => <span>data参数表示接收到的数据 -- {data}</span>}
</Consumer>

✨Props

children属性文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • children属性:表示组件标签的子节点。当组件标签有子节点时, props就会有该属性
  • children属性与普通的props一样,值可以是任意值(文本、 React元素、组件,甚至是函数)
function Hello (props) {
    return (
    	<div>
            组件的子节点{props.children}
        </div>
    )
}
<Hello>我是子节点</Hello>

props校验文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

props校验:允许在创建组件的时候,就指定props的类型、格式等文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 安装包prop-types
  2. 导入 prop-types 包
  3. 使用组件名.propTypes = {} 来给组件的props添加校验规则
  4. 校验规则通过ProTypes对象来指定
import PropTypes from 'prop-types'

function App(props) {
    return (
        <h1>Hi, {props.colors}</h1>
    )
}

App.propTypes = {
    // 约定colors属性为array类型
    // 如果类型不对, 则报出明确错误, 便于分析错误原因
    colors: PropTypes.array
}

约束规则:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 常见类型:array、bool、func、number、object、string
  • React元素类型:element
  • 必填项:isRequired
  • 特定结构的对象:shape({ })
// 常见类型
optionalFunc: PropTypes.func,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
})

默认值:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

给props设置默认值,在未传入props时生效文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

function App(props) {
    return (
        <div>
            此处展示props的默认值 {props.pageSize}
        </div>
    )
}
// 设置默认值
App.defaultProps = {
    pageSize: 10
}
// 不传入pageSize属性
<App />

✨组件的生命周期

只有类组件才有生命周期。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

挂载阶段文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • constructor
    • 传入参数为props,包含由父作用域传递过来的自定义属性和children(组件内部嵌套的视图结构),第一行的代码必须是super(props),调用父类的构造器函数。
    • 组件自有的state只能在这里定义。
    • 作用:继承父类、定义状态state、改变this指向。
  • render
    • 这个生命周期函数是必须要有的。这个生命周期函数横跨两个阶段,在挂载阶段和更新阶段都会执行。
    • 在组件初始化、调用this.setState()this.forceUpdate()props发生变化时执行。
    • 在更新阶段,如果当前组件有shouldComponentUpdate()并返回false时,render()将不执行。在挂载阶段,render()一定会执行,shouldComponentUpdate不影响render()
    • render内部,return不能使用this.setState()
  • componentDidMount
    • 相当于Vue中的Mounted,表示挂载阶段完成,各种业务逻辑都可在这里完成(DOM、ref、调接口、定时器)。
    • 这里可以使用多次this.setState(),默认是异步的。

更新阶段文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • render
  • componentDidUpdate
    • 相当于Vue中的Updated,表示更新阶段已完成。
    • 三种触发更新的方法:props变化、this.setState()this.forceUpdate()
    • 在这里可以使用this.setState(),但是必须给终止条件,否则造成死循环。

卸载阶段文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • componentWillUnmount
    • 相当于Vue中的beforeDestory,表示当前组件即将被销毁。
    • 作用:清除定时器、长链接、缓存等一些耗费性能和占用内存。
前端面试基础题:React JSX、高级组件和路由…

✨render-props和高阶组件(HOC)

如果两个组件中的部分功能相似或相同,该如何处理?
处理方式: 复用相似的功能(联想函数封装)
复用什么? 1. state 2. 操作state的方法 (组件状态逻辑 )
两种方式: 1. render props模式 2. 高阶组件(HOC)
注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

render-props模式文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 创建Mouse组件,在组件中提供复用的状态逻辑代码(状态、操作状态的方法)
  2. 将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
  3. 使用props.render()的返回值作为要渲染的内容
class Mouse extends React.Component {
    state = {
        x: 0,
        y: 0,
    }
    // 鼠标移动事件处理程序
    handleMouseMove = (e) => {
        this.setState({
            x: e.clientX,
            y: e.clientY
        })
    }
    // 监听鼠标移动
    componentDidMount() {
        window.addEventListener('mouseover', this.handleMouseMove);
    }
    render() {
        return this.props.render(this.state);
    }
}
class App extends React.Component { 
  render() {
    return (
      <div>
        <h1>render props模式</h1>
        <Mouse render={(mouse) => {
          return <p>鼠标位置{mouse.x} {mouse.y}</p>
        }} />
        <Mouse render={(mouse) => {
          return <img src={img} alt="猫" style={{
            position: 'absolute',
            top: mouse.y,
            left: mouse.x
          }} />
        }} />
      </div>
    )
  }
}
ReactDOM.render(<App/>, document.getElementById('root'))

高阶组件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

高阶组件(HOC,Higher-Order Component) 是一个函数,接收要包装的组件,返回增强后的组件高阶组件。
高级组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 创建一个函数,名称约定以with开头
  2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
  3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
  4. 在该组件中,渲染参数组件,同时将状态通过props传递给参数组件
// 创建高阶组件
function withMouse(WrappedComponent) {
  // 该组件提供复用的状态逻辑
  class Mouse extends React.Component {
    state = {
      x: 0, 
      y: 0
    }
    // 鼠标移动事件处理程序
    handleMouseMove = (e) => {
      this.setState({
        x: e.clientX,
        y: e.clientY
      })
    }
    // 监听鼠标移动
    componentDidMount() {
      window.addEventListener('mousemove', this.handleMouseMove)
    }
    // 解除事件监听
    componentWillUnmount() {
      window.removeEventListener('mousemove', this.handleMouseMove)
    }
    render() {
      return <WrappedComponent {...this.state}></WrappedComponent>
    }
  }
  return Mouse;
}
const Position = props => (
    <p>
    	鼠标当前位置(x: {props.x}, y: {props.y})
    </p>
)
// 获取增强后的组件
const MousePosition = withMouse(Position)

存在问题:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 得到的两个组件名称相同
    原因:默认情况下,React使用组件名称作为displayName
    解决方法:为高阶组件设置displayName便于调试时区分不用的组件
  2. props丢失
    原因:高阶组件没有往下传递props
    解决方法:渲染WrappedComponent时,将state和this.props一起传递给组件
// 问题一
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent) {
    return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

// 问题二
<WrappedComponent {...this.state} {...this.props} />

setState()

setState() 是异步更新数据的。注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()。可以多次调用 setState() ,只会触发一次重新渲染。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

推荐使用:setState((state, props) => {}[, callback])文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 参数state:表示最新的state
  • 参数props:表示最新的props
  • callback:在状态更新(页面完成重新渲染)后立即执行某个操作

setState的两个作用:1. 修改state,2. 更新组件(UI)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

过程:父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

this.state = { count: 1 }
this.setState({
  count: this.state.count + 1
})
console.log(this.state.count)  // 1


this.setState((state, props) => {
  return {
    count: state.count + 1
  }
})
this.setState((state, props) => {
  console.log('第二次调用:', state)  // 2
  return {
    count: state.count + 1
  }
})
console.log(this.state.count)  // 1

组件性能优化

  • 减轻state
    只存储跟组件渲染相关的数据(比如: count / 列表数据 / loading 等),不用做渲染的数据不要放在 state 中,比如定时器 id等,对于这种需要在多个方法中用到的数据,应该放在 this 中。
  • 避免不必要的重新渲染
    组件更新机制:父组件更新会引起子组件也被更新,子组件没有任何变化时也会重新渲染。
    解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
    作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染, false 表示不重新渲染
    触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate → render)
class Hello extends Component {
    shouldComponentUpdate() {
        // 根据条件,决定是否重新渲染组件
        return  false
    }
}
  • 纯组件
    PureComponentReact.Component功能相似,区别是PureComponent内部自动实现了shouldComponentUpdate钩子,不需要手动比较。
    原理:纯组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染组件
class Hello extends React.PureComponent {
    render () {
        return (
            <div>纯组件</div>
        )
    }
}

纯组件内部是浅层对比,对于引用类型来说,只比较对象的引用地址是否相同。state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

✨虚拟DOM和Diff算法

React部分更新的实现原理。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 初次渲染时, React 会根据初始state(Model),创建一个虚拟 DOM 对象(树) 。
  2. 根据虚拟 DOM 生成真正的 DOM,渲染到页面中。
  3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
  4. 与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。
  5. 最终, React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面。

✨路由

React路由时URL路径和组件的对应关系。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  1. 安装路由组件库:react-router-dom
  2. 导入核心组件:RouterRouteLink
  3. 使用Router组件包裹整个应用
  4. 使用Link组件作为导航菜单(路由入口)
  5. 使用Route组件配置路由规则和要展示的组件(路由出口)
import {HashRouter as Router, Routes, Route, Link}
​
<Router>
    <Link to="/about">about页面</Link>
    <Link to="/home">home页面</Link>
    <Routes>
        <Route path="/about" element={<About/>}/>
        <Route path="/home" element={<Home/>}/>
    </Routes>
</Router>

路由嵌套:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

<Routes>
  <Route path="invoices" element={<Invoices />}>
    <Route path=":invoiceId" element={<Invoice />} />
    <Route path="sent" element={<SentInvoices />} />
  </Route>
</Routes>

分别通过如下三种路径匹配:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • "/invoices"
  • "/invoices/123"
  • "/invoices/sent"

匹配模式:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html

  • 默认情况下, React 路由是模糊匹配模式
  • 模糊匹配规则:只要 pathname 以 path 开头就会匹配成功
    path 代表Route组件的path属性
    pathname 代表Link组件的to属性(也就是 location.pathname)
  • 精确匹配:只有当 path 和 pathname 完全匹配时才会展示该路由 (给默认路由添加 exact 属性)
    <Route exact path="/" element=... />
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/gcs/49424.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/gcs/49424.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定