网站seo设置是什么,酒泉网站建设设计,企业网站推广的方法有哪几种,手机网站案列React学习笔记 React系列笔记学习 上篇笔记地址#xff1a;【超全】React学习笔记 上#xff1a;基础使用与脚手架 下篇笔记地址#xff1a;【超全】React学习笔记 下#xff1a;路由与Redux状态管理 React进阶组件概念与使用
1. React 组件进阶导读
在掌握了 React 的基…React学习笔记 React系列笔记学习 上篇笔记地址【超全】React学习笔记 上基础使用与脚手架 下篇笔记地址【超全】React学习笔记 下路由与Redux状态管理 React进阶组件概念与使用
1. React 组件进阶导读
在掌握了 React 的基础知识后我们将进一步深入探讨 React 组件的进阶特性和技巧。这些进阶知识将帮助我们更好地理解和使用 React为构建复杂的前端应用奠定坚实的基础。下面是本阶段学习的主要目标和相关的导读内容
Props 接收数据
Props属性是 React 组件之间交互的主要方式之一。通过 Props我们可以将数据从父组件传递到子组件。理解和掌握 Props 的使用是 React 开发的基本技能。
function Welcome(props) {return h1Hello, {props.name}/h1;
}const element Welcome nameSara /;
ReactDOM.render(element, document.getElementById(root));父子组件之间的通讯
父子组件之间的通讯是 React 组件交互的基础。通常我们通过 Props 将数据从父组件传递到子组件通过回调函数将事件从子组件传递到父组件。
class ParentComponent extends React.Component {state { message: };handleMessage (message) {this.setState({ message });};render() {return (divChildComponent onMessage{this.handleMessage} /divMessage from child: {this.state.message}/div/div);}
}兄弟组件之间的通讯
在React中兄弟组件之间的通讯是相对复杂的一部分。由于React的数据流是单向的通常情况下组件之间的数据是从上至下传递的。当两个组件需要共享相同的数据或状态时通常的做法是将共享状态提升到它们共同的父组件中然后通过props将状态传递给它们。但是这种方法在组件树变得复杂时可能会变得很繁琐。为了解决这个问题我们可以使用一些状态管理库如Redux或者Context API。
通过共同父组件传递数据这种方式是将两个兄弟组件需要共享的数据提升到它们的共同父组件的状态中然后通过props将数据传递给这两个兄弟组件。
Props 校验
为确保组件的正确使用我们可以为组件的 Props 添加类型校验。React 提供了 propTypes 库来帮助我们实现这一目标。
import PropTypes from prop-types;class CustomComponent extends React.Component {// ...
}CustomComponent.propTypes {name: PropTypes.string.isRequired,age: PropTypes.number
};生命周期钩子函数
生命周期钩子函数允许我们在组件的不同阶段执行特定的操作例如在组件挂载、更新或卸载时。
class LifecycleExample extends React.Component {componentDidMount() {// 组件挂载时执行}componentDidUpdate(prevProps, prevState) {// 组件更新时执行}componentWillUnmount() {// 组件卸载时执行}render() {// ...}
}高阶组件 (Higher-Order Components, HOC)
高阶组件是一种用于复用组件逻辑的高级技术。通过高阶组件我们可以将共享逻辑抽取出来应用到其他组件上。
function withLogging(WrappedComponent) {return class extends React.Component {componentDidMount() {console.log(${WrappedComponent.name} is mounted);}render() {return WrappedComponent {...this.props} /;}};
}const LoggedComponent withLogging(CustomComponent);通过以上的导读和示例你将会对 React 组件的进阶知识有一个基本的理解。在接下来的学习中我们将深入探讨每一个目标以确保你能够熟练掌握这些进阶技巧和知识。
2.组件通讯概念与操作
在React应用开发中组件是基本的构建块它们是独立、可复用的代码片段可以被组合来构建复杂的用户界面。每个组件都有自己的状态和属性而组件通讯是指在不同组件之间共享这些状态和属性的过程。组件通讯对于构建有组织、可维护和可扩展的React应用至关重要。下面我们将介绍React中几种常见的组件通讯方式。
2.1 组件通讯props概念介绍与基本使用
在React中组件是独立且封闭的代码结构它们不能直接访问或修改外部数据。为了实现组件之间的数据交换React提供了props属性机制。通过props组件可以接收外部传递的数据并在内部使用这些数据。下面我们将通过示例来详细介绍props的基本用法和组件通讯的基本概念。
在React中props属性是组件之间通信的主要手段它允许我们将数据从父组件传递给子组件。每个React组件都可以接收props并在内部使用这些props。下面我们来详细介绍组件props的特点和使用方法。
2.1.1 传递数据
在React中可以通过为组件标签添加属性来传递数据。每个属性对应一个数据项属性的名称就是数据项的名称属性的值就是数据项的值。
Hello nameJack age{19} /在上述代码中我们为Hello组件传递了两个数据项name和age。name的值是字符串Jackage的值是数字19。
2.1.2 接收数据
组件可以通过props对象接收外部传递的数据。函数组件和类组件的接收方式略有不同
函数组件 函数组件可以通过参数直接接收props对象。在组件内部可以通过props对象访问传递的数据。
function Hello(props) {console.log(props); // 输出{ name: Jack, age: 19 }return div接收到的数据: {props.name}, {props.age}/div;
}类组件 类组件可以通过this.props对象接收传递的数据。在类组件的方法中可以通过this.props对象访问传递的数据。
class Hello extends React.Component {render() {console.log(this.props); // 输出{ name: Jack, age: 19 }return div接收到的数据: {this.props.name}, {this.props.age}/div;}
}2.1.3 使用示例
以下是一个完整的示例展示了如何通过props传递和接收数据
// 引入React库
import React from react;// 定义函数组件
function Hello(props) {return div接收到的数据: 姓名 - {props.name}, 年龄 - {props.age}/div;
}// 使用组件并传递数据
const element Hello nameJack age{19} /;// 渲染组件
ReactDOM.render(element, document.getElementById(root));在这个示例中我们定义了一个Hello函数组件并通过name和age属性向其传递了数据。Hello组件接收到数据后将数据显示在页面上。
2.2 组件通讯props特点
多样化的数据类型: 可以传递任意类型的数据给组件包括基本数据类型如字符串、数字、布尔值复杂数据类型如对象、数组以及函数等。
MyComponent stringProphello numberProp{42} arrayProp{[1, 2, 3]} objectProp{{ key: value }} functionProp{() console.log(hello)} /只读性: props是只读的组件不能修改自己的props。尝试修改props中的值会导致错误。
function MyComponent(props) {props.stringProp new value; // Error: Cannot assign to read only property stringProp of objectreturn div{props.stringProp}/div;
}构造函数中的props: 在类组件的构造函数中应该将props传递给super()以确保this.props在构造函数中可用。
class Hello extends React.Component {constructor(props) {super(props); // 将props传递给父类构造函数console.log(this.props); // 输出: { age: 19 }}render() {return div接收到的数据: {this.props.age}/div;}
}// 使用组件并传递props
ReactDOM.render(Hello age{19} /, document.getElementById(root));在上述代码中Hello组件的构造函数接收一个props参数并将props传递给super()。这样Hello组件的实例就可以在构造函数和其他方法中通过this.props访问传递的props。
2.3 组件通讯props总结
通过上述介绍我们了解了React组件props的基本特点和使用方法。记住props是只读的不能在组件内部修改。同时应该始终将props传递给super()以确保this.props在类组件的构造函数和其他方法中可用。通过正确使用props可以实现组件之间的有效通信从而创建出功能丰富、结构清晰的React应用。
3. 组件通讯的三种方式
React 的组件模型为数据流提供了清晰的方向父组件可以将其状态作为属性传递给它的子组件这种单向数据流使得组件的数据传递和管理变得直接且易于理解。下面我们会依次介绍父组件传递数据给子组件、子组件传递数据给父组件和兄弟组件之间的通讯。
3.1 父组件传递数据给子组件
父组件向子组件传递数据是最基本也是最直接的组件通信方式。通过这种方式我们可以将父组件的state或props数据传递给子组件。
步骤:
父组件提供数据: 在父组件中定义需要传递给子组件的state数据。
class Parent extends React.Component {state { lastName : 王}// ...
}添加属性到子组件标签: 在子组件的标签上添加属性属性的值就是要传递的数据。
Child name{this.state.lastName}/子组件接收数据: 在子组件中通过props接收父组件传递的数据。
function Child(props){return div子组件接收到数据: {props.name}/div;
}完整示例:
class Parent extends React.Component {state { lastName : 王}render() {return (div传递数据给子组件:Child name{this.state.lastName}//div);}
}function Child(props){return div子组件接收到数据: {props.name}/div;
}ReactDOM.render(Parent /, document.getElementById(root));在这个例子中Parent组件通过Child name{this.state.lastName}/将lastName数据传递给Child组件Child组件通过props接收到了Parent组件传递的数据并在组件内部显示该数据。通过父子组件的数据传递我们可以实现组件之间的通信将数据从一个组件传递到另一个组件从而实现组件的复用和应用的功能拆分。
3.2 子组件传递数据给父组件
在某些情况下我们可能需要将子组件的数据传递回父组件。这通常通过在父组件中定义一个回调函数并将该回调函数作为props传递给子组件来实现。当子组件中的某些操作触发时子组件调用传递给它的回调函数并将需要传递的数据作为参数提供给该函数。
步骤:
父组件提供回调函数: 在父组件中定义一个回调函数该函数接收一个参数该参数是子组件需要传递给父组件的数据。
class Parent extends React.Component {getMsg (msg) {console.log(Received message from child:, msg);}// ...
}将回调函数传递给子组件: 通过props将回调函数传递给子组件。
Child getMsg{this.getMsg}/子组件调用回调函数: 在子组件中定义一个方法来触发回调函数并将需要传递的数据作为参数提供给回调函数。
class Child extends React.Component {state { childMsg: React }handleClick () {this.props.getMsg(this.state.childMsg);}render() {return (button onClick{this.handleClick}点我给父组件传递数据/button);}
}完整示例:
class Parent extends React.Component {getMsg (msg) {console.log(Received message from child:, msg);}render() {return (divChild getMsg{this.getMsg}//div);}
}class Child extends React.Component {state { childMsg: React }handleClick () {this.props.getMsg(this.state.childMsg);}render() {return (button onClick{this.handleClick}点我给父组件传递数据/button);}
}ReactDOM.render(Parent /, document.getElementById(root));在这个例子中Parent组件提供了一个getMsg回调函数并通过props将其传递给Child组件。Child组件中定义了一个handleClick方法该方法通过this.props.getMsg(this.state.childMsg)调用了传递给它的回调函数并将childMsg数据作为参数传递给该函数。这样当用户点击Child组件中的按钮时Parent组件的getMsg方法会被调用并接收到Child组件传递的数据。
3.3 兄弟组件之间的通讯
当多个兄弟组件需要共享某些状态时一个常见的解决方案是将共享的状态提升到它们的最近公共父组件中。这种技术通常被称为状态提升。公共父组件负责管理共享的状态并通过props将状态和用于操作状态的方法传递给兄弟组件。
思想:
状态提升将状态从子组件提升到公共父组件中由公共父组件统一管理并通过props将状态和操作状态的方法传递给需要它的子组件。
公共父组件职责:
提供共享状态。提供操作共享状态的方法。
兄弟组件职责:
通过props接收共享的状态或操作状态的方法并使用它们来实现组件的功能。
示例: 假设我们有两个兄弟组件分别是IncrementButton和DecrementButton以及一个显示计数值的Display组件。我们希望IncrementButton能够增加计数值DecrementButton能够减少计数值。
class Counter extends React.Component {state { count: 0 }increment () {this.setState(prevState ({ count: prevState.count 1 }));}decrement () {this.setState(prevState ({ count: prevState.count - 1 }));}render() {return (divIncrementButton increment{this.increment}/DecrementButton decrement{this.decrement}/Display count{this.state.count}//div);}
}class IncrementButton extends React.Component {render() {return (button onClick{this.props.increment}/button);}
}class DecrementButton extends React.Component {render() {return (button onClick{this.props.decrement}-/button);}
}class Display extends React.Component {render() {return (divCount: {this.props.count}/div);}
}ReactDOM.render(Counter /, document.getElementById(root));在这个示例中我们将共享的count状态提升到Counter组件中并通过props将状态和用于操作状态的increment和decrement方法传递给IncrementButton、DecrementButton和Display组件。这样无论用户点击哪个按钮Display组件都能显示正确的count值。
关系框架图: #mermaid-svg-fzm29hQhDoFTAym9 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-fzm29hQhDoFTAym9 .error-icon{fill:#552222;}#mermaid-svg-fzm29hQhDoFTAym9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fzm29hQhDoFTAym9 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-fzm29hQhDoFTAym9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fzm29hQhDoFTAym9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fzm29hQhDoFTAym9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fzm29hQhDoFTAym9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fzm29hQhDoFTAym9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fzm29hQhDoFTAym9 .marker.cross{stroke:#333333;}#mermaid-svg-fzm29hQhDoFTAym9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fzm29hQhDoFTAym9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fzm29hQhDoFTAym9 .cluster-label text{fill:#333;}#mermaid-svg-fzm29hQhDoFTAym9 .cluster-label span{color:#333;}#mermaid-svg-fzm29hQhDoFTAym9 .label text,#mermaid-svg-fzm29hQhDoFTAym9 span{fill:#333;color:#333;}#mermaid-svg-fzm29hQhDoFTAym9 .node rect,#mermaid-svg-fzm29hQhDoFTAym9 .node circle,#mermaid-svg-fzm29hQhDoFTAym9 .node ellipse,#mermaid-svg-fzm29hQhDoFTAym9 .node polygon,#mermaid-svg-fzm29hQhDoFTAym9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fzm29hQhDoFTAym9 .node .label{text-align:center;}#mermaid-svg-fzm29hQhDoFTAym9 .node.clickable{cursor:pointer;}#mermaid-svg-fzm29hQhDoFTAym9 .arrowheadPath{fill:#333333;}#mermaid-svg-fzm29hQhDoFTAym9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fzm29hQhDoFTAym9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fzm29hQhDoFTAym9 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-fzm29hQhDoFTAym9 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-fzm29hQhDoFTAym9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fzm29hQhDoFTAym9 .cluster text{fill:#333;}#mermaid-svg-fzm29hQhDoFTAym9 .cluster span{color:#333;}#mermaid-svg-fzm29hQhDoFTAym9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fzm29hQhDoFTAym9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} increment decrement count Counter: count IncrementButton DecrementButton Display 在上图中Counter组件是IncrementButton、DecrementButton和Display组件的公共父组件。Counter组件通过props将increment、decrement方法和count状态传递给子组件从而实现了兄弟组件之间的通讯。
数据流向流程图
在这个流程图中我们可以看到兄弟组件IncrementButton和DecrementButton通过调用父组件Counter提供的increment和decrement方法来影响共享的count状态。而Display组件则通过父组件Counter接收到更新后的count状态并显示它。这种通过公共父组件来实现兄弟组件之间数据通讯的方式是React中常用的状态管理模式。 #mermaid-svg-bCyhuhW52V0aywVy {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-bCyhuhW52V0aywVy .error-icon{fill:#552222;}#mermaid-svg-bCyhuhW52V0aywVy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bCyhuhW52V0aywVy .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-bCyhuhW52V0aywVy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bCyhuhW52V0aywVy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bCyhuhW52V0aywVy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bCyhuhW52V0aywVy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bCyhuhW52V0aywVy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bCyhuhW52V0aywVy .marker.cross{stroke:#333333;}#mermaid-svg-bCyhuhW52V0aywVy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bCyhuhW52V0aywVy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bCyhuhW52V0aywVy .cluster-label text{fill:#333;}#mermaid-svg-bCyhuhW52V0aywVy .cluster-label span{color:#333;}#mermaid-svg-bCyhuhW52V0aywVy .label text,#mermaid-svg-bCyhuhW52V0aywVy span{fill:#333;color:#333;}#mermaid-svg-bCyhuhW52V0aywVy .node rect,#mermaid-svg-bCyhuhW52V0aywVy .node circle,#mermaid-svg-bCyhuhW52V0aywVy .node ellipse,#mermaid-svg-bCyhuhW52V0aywVy .node polygon,#mermaid-svg-bCyhuhW52V0aywVy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bCyhuhW52V0aywVy .node .label{text-align:center;}#mermaid-svg-bCyhuhW52V0aywVy .node.clickable{cursor:pointer;}#mermaid-svg-bCyhuhW52V0aywVy .arrowheadPath{fill:#333333;}#mermaid-svg-bCyhuhW52V0aywVy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bCyhuhW52V0aywVy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bCyhuhW52V0aywVy .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-bCyhuhW52V0aywVy .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-bCyhuhW52V0aywVy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bCyhuhW52V0aywVy .cluster text{fill:#333;}#mermaid-svg-bCyhuhW52V0aywVy .cluster span{color:#333;}#mermaid-svg-bCyhuhW52V0aywVy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bCyhuhW52V0aywVy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} increment decrement increment count decrement count count Counter: Count IncrementButton DecrementButton Display 在这个图中:
IncrementButton通过调用父组件Counter的increment方法来增加count值。DecrementButton通过调用父组件Counter的decrement方法来减少count值。Display组件从父组件Counter接收count值并显示它。父组件Counter负责管理和更新count状态并将其传递给子组件。
4. Context多层嵌套的组件通讯方式
在复杂的React应用中我们通常会遇到需要在组件树中传递数据的情况而不仅仅是单纯的父子组件通讯。例如如果App组件想要传递数据给深层嵌套的Child组件通常的做法可能会导致多层的props传递这既不优雅也不易维护。为了解决这个问题React提供了Context API它允许我们在组件树中更加方便地传递和接收数据。
4.1 使用Context传递数据
Context API主要由两个组件组成Provider和Consumer。
1. 创建Context:
首先我们需要使用React.createContext方法创建一个Context。
const MyContext React.createContext();2. Provider组件:
Provider组件负责提供数据。我们将Provider组件放在组件树的外层并通过value属性传递我们想要共享的数据。
class App extends React.Component {state {theme: dark}render() {return (MyContext.Provider value{this.state.theme}Child //MyContext.Provider);}
}3. Consumer组件:
Consumer组件负责消费数据。我们可以在任何需要接收数据的组件内部使用Consumer组件来接收Provider组件提供的数据。
class Child extends React.Component {render() {return (MyContext.Consumer{theme divThe theme is {theme}/div}/MyContext.Consumer);}
}通过上述代码我们实现了在App组件和Child组件之间通过Context传递数据而无需手动在每层组件之间传递props。
4.2 总结:
跨组件通讯: 当两个组件是远方亲戚例如嵌套多层时可以使用Context实现组件通讯避免了繁琐的props逐层传递。提供和消费数据: Context提供了Provider和Consumer两个组件分别用于提供和消费数据使得数据的传递变得清晰和方便。应用场景: Context非常适用于那些需要在组件树中共享状态的场景例如主题切换、语言切换等。
5. Props 深入学习与使用
Props 是 React 组件的输入它们可以是任意的值包括简单的数据类型如字符串、数字和布尔值或复杂的数据类型如对象、数组和函数。其中一个特殊的 prop 是 children它表示组件标签的子节点。
5.1 children 属性
children 是一个特殊的 prop它代表了组件标签内部的内容。children 属性与普通的 props 一样它的值可以是任意类型包括文本、React 元素、组件甚至是函数。
基本用法:
当我们在 JSX 中嵌套组件时嵌套的内容将作为 children prop 传递给外层组件。
function Hello(props) {return (div组件的子节点: {props.children}/div);
}// 使用
Hello我是子节点/Hello在上述代码中Hello 组件接收一个 children prop其值为 “我是子节点”。然后Hello 组件在其渲染输出中包含这个 children prop从而显示 “组件的子节点: 我是子节点”。
高级用法:
children prop 还可以接收一个函数并将该函数作为一个渲染 prop 使用。渲染 prop 是一种将可配置性传递给组件的技术。
function RenderPropComponent(props) {return props.children(Render Prop);
}// 使用
RenderPropComponent{value div{value}/div}
/RenderPropComponent在上述代码中RenderPropComponent 组件接收一个 children prop该 prop 是一个函数。RenderPropComponent 组件调用这个函数并传递一个参数 ‘Render Prop’。然后这个函数返回一个 React 元素该元素随后被渲染。
通过 children prop我们可以实现组件之间的灵活交互甚至可以构建高级的组件这些组件能够接收和渲染任意内容。这种模式在 React 社区中非常流行并且被广泛用于构建灵活和可复用的组件。
5.2 Props 校验
Props 校验是一种在运行时检查传递给组件的 props 是否符合预期类型的机制。它对于开发和维护大型项目非常有用可以在早期发现和修复问题。
为什么要用Props校验
例如在正常使用中组件A应该接受传入组件B的数组以此使用map方式去渲染页面。但是组件B传入了一个整数类型的数据进入组件A组件A强行调用map方法就会发生了报错。这个时候我们就需要检查类型是否符合我们预期类型。
对于组件来说props是外来的无法保证组件使用者传入什么格式的数据如果传入的数据格式不对可能会导致组件内部报错关键问题:组件的使用者不知道明确的错误原因
function App(props)
{const arr props.colors;const lst arr.map(item, index) li keyindexitem/lireturn ul{lst}/ul
}App colors{1} / // App预期期待arr类型但是传入一个整型数字1使用步骤: 安装 prop-types 包 使用 npm 或 yarn 安装 prop-types 包。 yarn add prop-types
# 或
npm i prop-types导入 prop-types 包 import PropTypes from prop-types;为组件添加 props 校验规则 使用 ComponentName.propTypes 对象为组件的 props 添加校验规则。propTypes 是一个特殊的属性它告诉 React这个组件期望接收何种类型的 props。 function App(props) {return (h1Hi, {props.colors}/h1);
}// 设置 props 校验规则
App.propTypes {colors: PropTypes.array
};在这个示例中我们为 App 组件的 colors prop 指定了一个校验规则该规则要求 colors 必须是一个数组。如果 colors 不是数组React 将在控制台中显示警告。
进一步的校验
PropTypes 提供了许多其他校验器允许我们进行更详细的校验。例如我们可以要求某个 prop 是特定的 JavaScript 类型如 array、bool、func、number、object、string也可以要求它是一个 React 元素、或者是一个枚举类型的值。
App.propTypes {colors: PropTypes.arrayOf(PropTypes.string),isVisible: PropTypes.bool,onToggle: PropTypes.func,value: PropTypes.oneOfType([PropTypes.number,PropTypes.string]),theme: PropTypes.oneOf([light, dark]),customProp: (props, propName, componentName) {if (!/matchme/.test(props[propName])) {return new Error(Invalid prop \${propName}\ supplied to \${componentName}\. Validation failed.);}}
};在上述代码中我们展示了如何使用不同的 PropTypes 校验器来校验 props。这样我们可以确保组件的使用者提供正确的 props从而减少运行时错误的可能性。
5.2.1 Props 校验约束规则
在 React 应用中校验组件的 props 对于确保应用的健壮性和易维护性非常重要。prop-types库提供了一系列的验证器可以帮助我们确保组件接收到了正确类型的 props。常见约束规则如下
常见类型array、bool、func、number、object、string;React元素类型element;必填项isRequired;特定结构的对象shape({})
下面介绍一些常用的约束规则和示例 常见类型校验 使用 PropTypes 对象的属性来校验特定类型的 props。 import PropTypes from prop-types;function MyComponent(props) {// ...
}MyComponent.propTypes {optionalArray: PropTypes.array,optionalBool: PropTypes.bool,optionalFunc: PropTypes.func,optionalNumber: PropTypes.number,optionalObject: PropTypes.object,optionalString: PropTypes.string,
};React 元素类型校验 使用 PropTypes.element 校验 prop 是否是一个 React 元素。 MyComponent.propTypes {optionalElement: PropTypes.element,
};必填项校验 使用 isRequired 标识符指定某个 prop 是必须的。 MyComponent.propTypes {requiredFunc: PropTypes.func.isRequired,requiredAny: PropTypes.any.isRequired,
};特定结构的对象校验 使用 PropTypes.shape 校验 prop 是否符合指定的结构。 MyComponent.propTypes {optionalObjectWithShape: PropTypes.shape({color: PropTypes.string,fontSize: PropTypes.number,}),
};在这个示例中optionalObjectWithShape prop 必须是一个对象该对象有两个属性color 和 fontSize其中 color 必须是一个字符串fontSize 必须是一个数字。
这些校验规则提供了强大的方式来确保组件的 props 符合预期帮助开发者在早期捕捉可能的错误并清楚地知道每个组件期望的 props 类型。通过利用 prop-types 库的这些功能可以编写更健壮、更容易维护的 React 应用。
5.3 props默认值
在开发React组件时可能会遇到一些情况即使组件的使用者没有明确提供某些props组件也需要有一些基本的行为。这时可以为props设置默认值。这样如果使用者没有提供这些propsReact将使用默认值代替。
以下是如何为props设置默认值的示例
import React from react;function Pagination(props) {return (div每页显示条数: {props.pageSize}/div);
}// 设置默认值
Pagination.defaultProps {pageSize: 10
};export default Pagination;在上述代码中我们创建了一个Pagination组件该组件接受一个pageSize prop用于指定每页显示的条目数量。我们使用defaultProps静态属性为pageSize prop设置了默认值10。当组件的使用者没有提供pageSize prop时React将使用这个默认值。
现在当我们这样使用Pagination组件时
Pagination /即使我们没有提供pageSize prop组件也将显示“每页显示条数: 10”。
这种方法允许我们为组件的props提供合理的默认值确保组件在没有明确指定所有props的情况下仍能正常工作。同时它也为组件的使用者提供了更多的灵活性允许他们只在需要时提供props而不是始终提供所有props。
6. 组件的生命周期
6.1 组件的生命周期概述
组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等组件非预期情况处理。
React 组件的生命周期可以分为三大阶段挂载阶段Mounting、更新阶段Updating和卸载阶段Unmounting。组件从被创建到挂载到页面中运行在运行时我们可以更新组件信息再到组件不用时卸载的过程。在这些阶段中React 提供了不同的生命周期方法或称为生命周期钩子函数以便开发者在组件的不同时期执行特定的操作。
另外结合前面所学的**只有类组件才有生命周期。**因为函数组件的在创建之后就会被销毁了。 挂载阶段Mounting: constructor: 构造函数用于初始化组件的 state 和绑定事件处理函数等。static getDerivedStateFromProps: 在组件实例化后和重新渲染前调用返回一个对象来更新 state或返回 null 表示没有更新。render: 返回组件的 JSX 结构。componentDidMount: 组件挂载到 DOM 后立即调用通常用于发起网络请求或设置事件监听器。
class MyComponent extends React.Component {constructor(props) {super(props);this.state { data: null };}static getDerivedStateFromProps(nextProps, nextState) {// ...}componentDidMount() {fetch(/api/data).then(response response.json()).then(data this.setState({ data }));}render() {return (div{this.state.data ? this.state.data : Loading...}/div);}
}更新阶段Updating: static getDerivedStateFromProps: 同上。shouldComponentUpdate: 返回一个布尔值决定是否继续渲染周期。render: 同上。getSnapshotBeforeUpdate: 在最新的渲染输出提交到 DOM 前调用返回值将作为 componentDidUpdate 的第三个参数。componentDidUpdate: 组件更新后调用通常用于在更新后执行网络请求或 DOM 操作。
class MyComponent extends React.Component {shouldComponentUpdate(nextProps, nextState) {return nextProps.value ! this.props.value;}componentDidUpdate(prevProps, prevState, snapshot) {// ...}render() {return (div{this.props.value}/div);}
}卸载阶段Unmounting: componentWillUnmount: 组件卸载前调用通常用于清理事件监听器或取消网络请求等。
class MyComponent extends React.Component {componentWillUnmount() {// 清理操作如取消事件监听、网络请求等}render() {return (div{this.props.value}/div);}
}以上各阶段的生命周期方法为开发者提供了在不同时机操作组件的能力从而能够实现更复杂的功能以及在必要时进行性能优化或资源清理等操作。
6.2 生命周期的三个阶段 - 创建时挂载阶段
在创建挂载阶段组件会经历 constructor → render → componentDidMount 的执行顺序。下面是这个阶段各生命周期方法的流程图和详细描述
在此阶段组件实例被创建并插入到 DOM 中。以下是这个阶段中的生命周期方法的执行顺序和作用
流程图: #mermaid-svg-SOJcoAiNfKEuAPAZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SOJcoAiNfKEuAPAZ .error-icon{fill:#552222;}#mermaid-svg-SOJcoAiNfKEuAPAZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SOJcoAiNfKEuAPAZ .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-SOJcoAiNfKEuAPAZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SOJcoAiNfKEuAPAZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SOJcoAiNfKEuAPAZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SOJcoAiNfKEuAPAZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SOJcoAiNfKEuAPAZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SOJcoAiNfKEuAPAZ .marker.cross{stroke:#333333;}#mermaid-svg-SOJcoAiNfKEuAPAZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SOJcoAiNfKEuAPAZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SOJcoAiNfKEuAPAZ .cluster-label text{fill:#333;}#mermaid-svg-SOJcoAiNfKEuAPAZ .cluster-label span{color:#333;}#mermaid-svg-SOJcoAiNfKEuAPAZ .label text,#mermaid-svg-SOJcoAiNfKEuAPAZ span{fill:#333;color:#333;}#mermaid-svg-SOJcoAiNfKEuAPAZ .node rect,#mermaid-svg-SOJcoAiNfKEuAPAZ .node circle,#mermaid-svg-SOJcoAiNfKEuAPAZ .node ellipse,#mermaid-svg-SOJcoAiNfKEuAPAZ .node polygon,#mermaid-svg-SOJcoAiNfKEuAPAZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SOJcoAiNfKEuAPAZ .node .label{text-align:center;}#mermaid-svg-SOJcoAiNfKEuAPAZ .node.clickable{cursor:pointer;}#mermaid-svg-SOJcoAiNfKEuAPAZ .arrowheadPath{fill:#333333;}#mermaid-svg-SOJcoAiNfKEuAPAZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SOJcoAiNfKEuAPAZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SOJcoAiNfKEuAPAZ .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-SOJcoAiNfKEuAPAZ .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-SOJcoAiNfKEuAPAZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SOJcoAiNfKEuAPAZ .cluster text{fill:#333;}#mermaid-svg-SOJcoAiNfKEuAPAZ .cluster span{color:#333;}#mermaid-svg-SOJcoAiNfKEuAPAZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SOJcoAiNfKEuAPAZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} step 1: constructor step 2: render step 3: componentDidMount 表格描述:
钩子函数名称触发时机作用constructor创建组件时最先执行初始化 state为事件处理程序绑定 thisrender每次组件渲染都会触发渲染 UI (注意:不能调用setState)componentDidMount组件挂载完成DOM渲染)后发送网络请求DOM 操作设置事件监听器等
代码示例
class MyComponent extends React.Component {constructor(props) {super(props);this.state { data: null };this.handleEvent this.handleEvent.bind(this);}componentDidMount() {fetch(/api/data).then(response response.json()).then(data this.setState({ data }));}handleEvent() {// event handling logic}render() {return (div onClick{this.handleEvent}{this.state.data ? this.state.data : Loading...}/div);}
}在上述示例中我们首先在 constructor 中初始化了 state 并绑定了事件处理函数 handleEvent。随后在 componentDidMount 中发起了一个网络请求来获取数据。最后render 方法负责渲染 UI其中包含了一个点击事件处理器 handleEvent。
6.2 生命周期的三个阶段 - 更新时 (更新阶段)
render更新执行时机∶
setState()forceUpdate()组件接收到新的props.
在此阶段由于 props 的变化、state 的更新或者父组件的重新渲染等原因组件可能需要更新。
以下是这个阶段中的生命周期方法的执行顺序和作用
流程图: #mermaid-svg-Cvt7idEccnsDRIO8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Cvt7idEccnsDRIO8 .error-icon{fill:#552222;}#mermaid-svg-Cvt7idEccnsDRIO8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Cvt7idEccnsDRIO8 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-Cvt7idEccnsDRIO8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Cvt7idEccnsDRIO8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Cvt7idEccnsDRIO8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Cvt7idEccnsDRIO8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Cvt7idEccnsDRIO8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Cvt7idEccnsDRIO8 .marker.cross{stroke:#333333;}#mermaid-svg-Cvt7idEccnsDRIO8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Cvt7idEccnsDRIO8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Cvt7idEccnsDRIO8 .cluster-label text{fill:#333;}#mermaid-svg-Cvt7idEccnsDRIO8 .cluster-label span{color:#333;}#mermaid-svg-Cvt7idEccnsDRIO8 .label text,#mermaid-svg-Cvt7idEccnsDRIO8 span{fill:#333;color:#333;}#mermaid-svg-Cvt7idEccnsDRIO8 .node rect,#mermaid-svg-Cvt7idEccnsDRIO8 .node circle,#mermaid-svg-Cvt7idEccnsDRIO8 .node ellipse,#mermaid-svg-Cvt7idEccnsDRIO8 .node polygon,#mermaid-svg-Cvt7idEccnsDRIO8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Cvt7idEccnsDRIO8 .node .label{text-align:center;}#mermaid-svg-Cvt7idEccnsDRIO8 .node.clickable{cursor:pointer;}#mermaid-svg-Cvt7idEccnsDRIO8 .arrowheadPath{fill:#333333;}#mermaid-svg-Cvt7idEccnsDRIO8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Cvt7idEccnsDRIO8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Cvt7idEccnsDRIO8 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-Cvt7idEccnsDRIO8 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-Cvt7idEccnsDRIO8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Cvt7idEccnsDRIO8 .cluster text{fill:#333;}#mermaid-svg-Cvt7idEccnsDRIO8 .cluster span{color:#333;}#mermaid-svg-Cvt7idEccnsDRIO8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Cvt7idEccnsDRIO8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} return true getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate 表格描述:
钩子函数名称触发时机作用getDerivedStateFromProps在render方法前包括初次渲染和后续更新返回对象来更新state或返回null表示无需更新shouldComponentUpdate在渲染前返回false会跳过后续渲染过程返回 false 来阻止渲染用于优化性能render每次组件渲染都会触发渲染 UI (注意:不能调用setState)getSnapshotBeforeUpdate在最新的渲染输出提交给DOM之后立即执行从 DOM 捕获一些信息以供 componentDidUpdate 使用componentDidUpdate在更新后立即调用首次渲染不会执行通常用于网络请求和DOM操作注意如果要setState()必须放在if条件下
代码示例
class UpdateComponent extends React.Component {static getDerivedStateFromProps(nextProps, prevState) {if (nextProps.value ! prevState.value) {return { value: nextProps.value }; // 更新state}return null; // 无需更新}shouldComponentUpdate(nextProps, nextState) {return nextProps.value ! this.props.value; // 若prop值未变化则阻止渲染}componentDidUpdate(prevProps, prevState, snapshot) {// 通常在此执行网络请求或DOM操作}render() {return div{this.state.value}/div;}
}在这个示例中getDerivedStateFromProps 方法检查新的 props.value 是否有变化如果有则更新 state.value。shouldComponentUpdate 方法用于比较新旧 props.value如果它们相同则阻止组件渲染。componentDidUpdate 通常用于网络请求和DOM操作。
componentDidUpdate注意事项
componentDidUpdate是一个在组件更新后立即调用的生命周期方法。它是在render方法之后最新的渲染输出被提交到DOM之后立即执行的。这是一个好地方去执行可能需要DOM的任何代码。你也可能会需要在这里做网络请求但要确保你有一个条件来避免无限循环。如果你需要更新数据在调用setState时你必须包裹它在一个条件语句中以确保不会触发无限的更新循环。
下面是一个示例展示如何在componentDidUpdate中执行网络请求和DOM操作并正确地使用setState:
class MyComponent extends React.Component {constructor(props) {super(props);this.state {data: null,searchTerm: };}componentDidUpdate(prevProps, prevState) {// 检查是否有新的搜索词避免无限循环if (prevState.searchTerm ! this.state.searchTerm) {// 发起网络请求fetch(https://api.example.com/data?search this.state.searchTerm).then(response response.json()).then(data this.setState({ data }));}// DOM 操作示例if (this.state.data !prevState.data) {document.title New Data Received;}// 如果需要更新状态请确保它在条件语句中if (someCondition) {this.setState({ /* ... */ });}}handleSearch (event) {this.setState({ searchTerm: event.target.value });}render() {return (divinput typetext onChange{this.handleSearch} /{/* ... */}/div);}
}在这个示例中我们在componentDidUpdate中检查searchTerm是否有变化如果有我们发起一个网络请求。同时我们检查data状态是否有变化如果有我们更新文档的标题。当满足某些条件时我们也在componentDidUpdate中调用setState但确保它是在一个条件语句中以避免无限循环。
6.3 生命周期的三个阶段 - 销毁时 (销毁阶段)
当组件即将从DOM中被移除时componentWillUnmount生命周期方法将会被调用。这是执行任何必要清理的好时机比如无效的定时器、取消网络请求或清理任何在componentDidMount中创建的订阅等。
下面是一个示例展示了如何在componentWillUnmount方法中清理一个定时器
class TimerComponent extends React.Component {constructor(props) {super(props);this.state {time: 0};}// 当组件挂载时创建一个定时器componentDidMount() {this.timerID setInterval(() this.tick(),1000 // 每秒更新一次);}// 当组件即将卸载时清除定时器componentWillUnmount() {clearInterval(this.timerID);}// 更新组件的state触发重新渲染tick() {this.setState((prevState) ({time: prevState.time 1}));}render() {return (divh1Elapsed Time: {this.state.time} seconds/h1/div);}
}// 在其他地方使用TimerComponent
// ...在上述示例中我们在componentDidMount生命周期方法中创建了一个定时器并在componentWillUnmount生命周期方法中清除了定时器。这样确保了不会有任何泄漏例如如果定时器继续运行即使组件不再在DOM中也无法清除它那将是一个问题。通过在componentWillUnmount中清除定时器我们可以确保当组件被卸载时释放所有的资源。
7. render-props 和高阶组件 (HOC)
7.1 React组件复用概述
在 React 应用中复用代码是非常重要的。特别是有时候我们会在多个组件中遇到相似或相同的功能逻辑这时候就需要考虑如何将这些逻辑抽离出来形成可复用的代码。React 社区中主要有两种方式来实现组件逻辑的复用render-props 和 高阶组件 (Higher-Order Components, HOC)。这两种方式允许我们在不同的组件中复用某些逻辑而不必改变组件的结构。
思考︰如果两个组件中的部分功能相似或相同该如何处理?
处理方式∶复用相似的功能联想函数封装)
复用什么?
state设置状态操作state的方法组件状态逻辑)
两种方式
render props模式高阶组件(HOC )
注意∶这两种方式不是新的APi而是利用React自身特点的编码技巧演化而成的固定模式(写法)
7.2 Render Props 模式
Render Props 模式是 React 中的一个非常强大的模式它允许我们在不同的组件中共享某些状态或逻辑同时还能保留组件的灵活性使得我们可以自定义渲染的 UI。下面通过一个实际的示例来深入了解 Render Props 模式的运用。
思路分析: 问题1: 如何拿到该组件中复用的 state? 解决方案: 我们可以创建一个组件并在这个组件中封装我们想要共享的 state 和操作 state 的方法。然后我们提供一个函数作为 prop这个函数接受封装的 state 作为参数。 问题2: 如何渲染任意的 UI? 解决方案: 使用该函数的返回值作为要渲染的 UI 内容。这样我们就能在函数中自由地定义我们想要渲染的 UI并且可以访问到共享的 state。
另外在Render Props模式中props.render是一个约定它是通过组件的props传递一个函数到组件内部然后在组件内部调用这个函数并传递一些参数给它最后将这个函数的返回值渲染到DOM中所以这意味着你可以使用其他名字代替render只要你能在需要使用函数的地方正确的调用它与传入它。
示例:
假设我们有一个Mouse组件它能够追踪用户的鼠标位置。我们想把这个鼠标追踪的功能复用在不同的组件中但是渲染的内容可能不同这时我们可以使用Render Props模式来实现
import React, { Component } from react;class Mouse extends Component {state { x: 0, y: 0 };handleMouseMove (event) {this.setState({x: event.clientX,y: event.clientY});}render() {return (div style{{ height: 100vh }} onMouseMove{this.handleMouseMove}{this.props.render(this.state)}/div);}
}function App() {return (Mouse render{mouse (p鼠标的位置是 {mouse.x}, {mouse.y}/p)}/);
}export default App;在上面的代码中:
Mouse 组件通过 onMouseMove 事件来追踪用户的鼠标位置并将位置信息保存在 state 中。Mouse 组件接受一个名为 render 的 prop这个 prop 是一个函数。在 Mouse 组件的 render 方法中我们调用 this.props.render 函数并将 this.state 作为参数传递给它。这样我们就能够在外部获取到 Mouse 组件内部的 state 信息。在 App 组件中我们使用 Mouse 组件并提供一个 render prop。这个 render prop 是一个函数它接受 mouse 参数并返回一个 React 元素。通过这种方式我们就能够自定义渲染的 UI并且可以访问到 Mouse 组件内部的 state 信息。
这个示例展示了如何通过 render props 模式来复用组件逻辑同时保持组件的灵活性和可定制性。在实际开发中render props 模式是一个非常有用的模式它能够帮助我们更好地组织和复用代码。
7.2 高阶组件 (Higher-Order Components, HOC)
高阶组件High Order Component, HOC是React中用于组件逻辑复用的一种模式。它是一个接收组件并返回新组件的函数。通过这种方式可以在不同的组件间共享相同的逻辑。在使用高阶组件时通常需要遵循以下步骤
创建高阶组件
创建一个函数这个函数是高阶组件的主体它会接收一个组件作为参数并返回一个新的组件。通常约定是以with为前缀来命名这个函数。
function withExampleFeature(WrappedComponent) {// ...
}指定函数参数函数的参数应该是一个组件通常以大写字母开头来命名例如WrappedComponent。
function withExampleFeature(WrappedComponent) {// ...
}在函数内部创建一个类组件在这个类组件内可以定义和管理你想要复用的状态和逻辑。
function withExampleFeature(WrappedComponent) {return class extends React.Component {// 这里可以定义和管理状态state {featureEnabled: false,// ...};// 可以定义任何你想要复用的逻辑enableFeature () {this.setState({ featureEnabled: true });};// ...};
}在类组件内渲染WrappedComponent同时将你想要共享的状态和逻辑通过props传递给WrappedComponent。
function withExampleFeature(WrappedComponent) {return class extends React.Component {// ...render() {// 将状态和逻辑传递给 WrappedComponentreturn WrappedComponent {...this.props} {...this.state} enableFeature{this.enableFeature} /;}};
}使用高阶组件
调用高阶组件传递你想要增强的组件作为参数并将返回的新组件渲染到页面上。
const EnhancedComponent withExampleFeature(OriginalComponent);// 在你的组件树中使用 EnhancedComponent
EnhancedComponent /为高阶组件设置displayName
当你使用高阶组件时可能会发现在React Developer Tools中包装后的组件和原始组件具有相同的displayName。为了避免混淆并使得调试更为简单可以为高阶组件设置一个明确的displayName。
function withExampleFeature(WrappedComponent) {class WithExampleFeature extends React.Component {// ...}WithExampleFeature.displayName WithExampleFeature(${getDisplayName(WrappedComponent)});return WithExampleFeature;
}function getDisplayName(WrappedComponent) {return WrappedComponent.displayName || WrappedComponent.name || Component;
}在上面的代码中WithExampleFeature.displayName设置了一个明确的displayName该displayName包含了原始组件的名称。getDisplayName函数是一个简单的辅助函数用于获取组件的名称。这样在React Developer Tools中你就能明确地看到每个组件是由哪个高阶组件包装的。
解决props丢失问题
在使用高阶组件HOC时需要注意props的传递问题。如果不正确地传递props那么被包装的组件可能无法访问到外部传递给高阶组件的props这会导致预期之外的行为。为了解决这个问题应该确保在渲染被包装组件时传递所有接收到的props。
正确传递props的关键是使用JavaScript的展开操作符…来传递props和state。
下面是一个例子展示了如何在高阶组件中正确传递props
function withExampleFeature(WrappedComponent) {return class extends React.Component {state {featureEnabled: false,};enableFeature () {this.setState({ featureEnabled: true });};render() {// 使用展开操作符将this.props和this.state传递给WrappedComponentreturn WrappedComponent {...this.props} {...this.state} enableFeature{this.enableFeature} /;}};
}const EnhancedComponent withExampleFeature(OriginalComponent);// 使用EnhancedComponent并传递props
EnhancedComponent somePropvalue /在上述代码中withExampleFeature是一个高阶组件它返回一个新的组件类。在这个新组件的render方法中我们使用展开操作符...将this.props和this.state传递给WrappedComponent。这样无论外部如何传递propsWrappedComponent都能接收到所有的props和state。
这种方式确保了WrappedComponent能够接收到所有从外部传递来的props以及高阶组件内部管理的state和方法。通过这种方式可以解决高阶组件中的props丢失问题确保被包装组件能够正确地工作。
8. useEffect钩子函数使用
useEffect的概念理解
useEffect是一个React Hook函数用于在React组件中创建不是由事件引起而是由渲染本身引起的操作比如发送AJAX请求更改DOM等等。 说明:上面的组件中没有发生任何的用户事件组件渲染完毕之后就需要和服务器要数据整个过程属于**“只由渲染引起的操作”** 。
useEffect的基础使用
需求:在组件渲染完毕之后立刻从服务端获取频道列表数据并显示到页面中语法:
useEffect((){}, [])参数1是一个函数可以把它叫做副作用函数在函数内部可以放置要执行的操作参数2是一个数组(可选参)在数组里放置依赖项不同依赖项会影响第一个参数函数的执行当是一个空数组的时候副作用函数只会在组件渲染完毕之后执行一次.
useEffect依赖项参数说明
useEffect副作用函数的执行时机存在多种情况根据传入依赖项的不同会有不同的执行表现
依赖项副作用函数调用时机没有依赖项组件初始化渲染组件更新时渲染空数组依赖项只有组件初始化时渲染添加特定依赖项组件初始化渲染特定依赖项发生变化时
参考代码
import {userEffect, useState} from reactconst App ()
{// 1. 没有依赖项初始化组件更新时const [count, setCount] useState(0);useEffect(() {console.log(副作用函数执行了)});// 2.传入空数组依赖项仅在初始执行一次useEffect((){console.log(副作用函数执行了)}, []);// 3. 传入特定依赖项初始化依赖项发生变化时执行useEffect((){console.log(副作用函数执行了)}, [count]);return (divbutton onClick{()setCount(count 1)}{count}/button/div);
}export default App;useEffect-清理副作用
在useEffect中编写的由渲染本身引起的对接组件外部的操作社区也经常把它叫做副作用操作比如在useEffect中开启了一个定时器我们想在组件卸载时把这个定时器再清理掉这个过程就是清理副作用。
语法格式
useEffect(()
{// 副作用函数逻辑return () {// 清理副作用函数逻辑}
}, [])说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行。
示例代码
import {useEffect, useState} from react;const Son ()
{useEffect(// 1. 渲染时开启一个定时器() {// 副作用函数逻辑const timer setInterval(() {console.log(定时器执行中)}, 1000)// 2.返回副作用return () {// 副作用函数逻辑clearInterval(timer)}}, [])return divthis is Son/div
}const App ()
{const [show, setShow] useState(true)return (div{show Son /}button onClick{()setShow(false)}卸载Son组件/button/div)
}useEffect 具有以下优势
控制副作用执行时机useEffect 提供了一种在组件渲染后执行副作用例如数据获取、订阅或者手动更改 DOM的方式。这样可以确保你的副作用在 DOM 更新完毕后执行避免了因 DOM 还未准备好而导致的错误。依赖项数组useEffect 的第二个参数是一个依赖项数组它告诉 React 只有在依赖项发生变化时才重新执行副作用。在上述代码中依赖项数组包含 dispatch这意味着只有当 dispatch 函数发生变化时useEffect 内的代码才会重新执行。由于 dispatch 函数通常不会变化所以 useEffect 内的代码基本上只会在组件第一次渲染时执行。
9. React组件进阶总结
React的组件进阶主要涵盖了组件通讯、props的应用、状态提升、组件生命周期、钩子函数、render props模式、高阶组件以及组件的简洁模型。下面分别总结这些方面的核心内容
组件通讯
组件通讯是React应用的基础主要分为以下几种 父子组件通讯 父组件通过props向子组件传递数据子组件通过回调函数向父组件传递数据。 // 父组件
class Parent extends React.Component {state { data: hello };render() {return Child data{this.state.data} /;}
}
// 子组件
function Child(props) {return div{props.data}/div;
}兄弟组件通讯 兄弟组件间的通讯通常通过共同的父组件来中转或使用状态管理库如 Redux。 // 公共父组件
class CommonParent extends React.Component {state { sharedData: hello };render() {return (SiblingOne data{this.state.sharedData} /SiblingTwo data{this.state.sharedData} //);}
}Props的应用
Props校验通过prop-types库进行props的类型校验以确保组件接收到正确格式的props。Props默认值通过defaultProps为props设置默认值确保在未传入props时组件能正常工作。
状态提升
状态提升是一种将状态数据提升到公共父组件然后通过props将状态传递给子组件的模式。它可以解决多个组件需要共享状态的问题。
组件生命周期
理解组件的生命周期是掌握React的关键包括挂载阶段、更新阶段和卸载阶段以及在这些阶段中可以使用的钩子函数如componentDidMount, componentDidUpdate和componentWillUnmount。
钩子函数
钩子函数提供了在特定时机执行某些操作的能力如在组件挂载后发送网络请求等。
Render Props模式和高阶组件
Render Props通过一个函数prop向子组件传递数据使得子组件可以灵活渲染不同的内容。高阶组件HOC通过包装组件的方式共享组件逻辑使得组件更加复用和模块化。
// 高阶组件示例
function withEnhancement(WrappedComponent) {return class extends React.Component {render() {return WrappedComponent {...this.props} enhancedPropenhanced /;}};
}const EnhancedComponent withEnhancement(OriginalComponent);组件的极简模型
React组件的核心是根据state和props渲染UI即(state, props) UI。理解这一模型有助于编写简洁、高效的React代码。
通过以上的总结和示例可以更好地理解React组件的进阶概念为构建复杂的React应用奠定基础。
React原理机制学习
1. 引入
在深入学习React框架的时候理解其背后的机制原理是非常重要的。这不仅仅可以帮助我们编写出更高效、更可靠的代码而且也可以在遇到问题时更快地定位并解决问题。在这个阶段我们将深入探讨React的核心机制和原理包括以下几个重要的方面
异步的setState()
setState()方法是React中最常用的方法之一它用于更新组件的状态。然而setState()并不是立即更新状态而是异步的。理解其异步的机制有助于我们避免因为依赖于立即更新状态而导致的一些常见错误。
JSX语法的转化过程
JSX是React的一种语法糖它让我们可以用类似于XML的语法来描述组件的结构。但是JSX最终会被转化为JavaScript代码。了解这个转化过程可以帮助我们更好地理解React是如何工作的。
React组件的更新机制
React组件的更新机制是保证其性能和效率的关键。理解组件何时以及为什么会更新以及如何控制组件的更新对于编写高效的React应用是非常重要的。
组件性能优化
通过某些优化技巧如使用shouldComponentUpdate或React.memo我们可以提高React应用的性能。这一部分我们将深入探讨如何对React组件进行性能优化。
虚拟DOM和Diff算法
虚拟DOM是React高效的核心而Diff算法则是虚拟DOM的基础。通过理解虚拟DOM和Diff算法的原理我们可以更好地理解React为何能提供如此高的渲染效率。
通过探讨以上的核心机制和原理我们将能够更加深入地理解React的工作方式从而编写出更高效、更可维护的React应用。
2. setState()的说明
React中的setState()方法是用于更新组件状态的主要方式。它提供了一个机制使我们能够以声明式的方式描述组件的状态应该如何随时间变化。下面我们将探讨setState()的一些核心特点和使用方法。
2.1 更新数据
异步更新
setState()是异步更新数据的。这意味着在调用setState()后状态不会立即更新。而是React会将setState()调用放入一个队列中并在稍后的一个更合适的时间点统一处理这些更新。这种异步的机制有助于优化性能减少不必要的渲染。
this.state { count: 1 };
this.setState({ count: this.state.count 1 });
console.log(this.state.count); // 输出: 1在上面的示例中由于setState()是异步的所以在setState()调用之后立即打印this.state.count结果仍然是1而不是2。
2.2 推荐语法
在处理setState()时推荐使用函数式更新这种方式可以确保你的状态更新是基于最新的状态和属性。函数式更新接受两个参数最新的state和props并应返回一个对象来更新状态。下面是使用函数式更新的一个示例
this.setState((state, props) {return {count: state.count 1};
});// 在函数式更新之后状态不会立即更新
console.log(this.state.count); // 输出: 1在上面的代码中我们将一个函数传递给setState()而不是一个对象。函数接收最新的state和props作为参数并返回一个对象该对象包含我们想要更新的状态值。
2.3 第二个参数
setState()也接受一个可选的回调函数作为它的第二个参数。这个回调函数会在状态更新和组件重新渲染完成后被调用。这可以用于在状态更新后立即执行某些操作。
this.setState((state, props) {return { count: state.count 1 };},() {console.log(这个回调函数会在状态更新后立即执行);}
);// 或者可以使用它来更新文档标题
this.setState((state, props) { return { count: state.count 1 }; },() { document.title 更新state后的标题: this.state.count; }
);在上述代码中我们展示了如何使用setState()的回调函数来在状态更新后执行操作。这可以是任何你想要在状态更新后立即执行的操作例如更新文档的标题或执行其他的副作用。
2.3 批量更新
React有一个优化机制可以将多个setState()调用合并成一个以减少渲染的次数。这意味着即使你多次调用setState()React也只会触发一次重新渲染。
this.setState({ count: this.state.count 1 });
this.setState({ count: this.state.count 1 });
this.setState({ count: this.state.count 1 });
// 结果: count只会增加1而不是3在上面的示例中尽管我们调用了三次setState()但count的值只会增加1而不是3。这是因为React将这三个setState()调用合并成了一个只触发了一次重新渲染。
3. JSX语法的转化过程
在React中JSX只是React.createElement()方法的语法糖它提供了一种更加简洁、易读的方式来创建React元素。但在背后JSX代码需要通过Babel插件babel/preset-react被转译成React.createElement()调用。下面是JSX语法转化的三个主要步骤
编写JSX:
const element (h1 classNamegreetingHello JSX!/h1
);JSX转化为createElement()调用:
const element React.createElement(h1,{ className: greeting },Hello JSX!
);createElement()返回React元素:
// 注意: 这是简化过的结构
const element {type: h1,props: {className: greeting,children: Hello JSX!}
};在这个过程中首先我们编写了JSX代码。然后Babel插件babel/preset-react将JSX代码转化为React.createElement()调用。最后React.createElement()方法返回一个React元素对象该对象描述了我们想要在屏幕上渲染的内容。
4. 组件更新机制
setState()方法在React组件中有两大核心作用
修改组件的state状态;触发组件及其子组件的更新和重新渲染 (UI 更新)。当一个父组件被重新渲染时它的所有子组件也会被重新渲染但只是在当前组件子树中包括当前组件及其所有子组件。
下面的树状结构图示展示了一个**三层二叉树的原始结构和更新过程。**在更新过程中我们将更新第二层的右侧子树并突出显示被更新的节点组件。
原始结构: #mermaid-svg-B2APVnyk3JaB5toC {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-B2APVnyk3JaB5toC .error-icon{fill:#552222;}#mermaid-svg-B2APVnyk3JaB5toC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-B2APVnyk3JaB5toC .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-B2APVnyk3JaB5toC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-B2APVnyk3JaB5toC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-B2APVnyk3JaB5toC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-B2APVnyk3JaB5toC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-B2APVnyk3JaB5toC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-B2APVnyk3JaB5toC .marker.cross{stroke:#333333;}#mermaid-svg-B2APVnyk3JaB5toC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-B2APVnyk3JaB5toC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-B2APVnyk3JaB5toC .cluster-label text{fill:#333;}#mermaid-svg-B2APVnyk3JaB5toC .cluster-label span{color:#333;}#mermaid-svg-B2APVnyk3JaB5toC .label text,#mermaid-svg-B2APVnyk3JaB5toC span{fill:#333;color:#333;}#mermaid-svg-B2APVnyk3JaB5toC .node rect,#mermaid-svg-B2APVnyk3JaB5toC .node circle,#mermaid-svg-B2APVnyk3JaB5toC .node ellipse,#mermaid-svg-B2APVnyk3JaB5toC .node polygon,#mermaid-svg-B2APVnyk3JaB5toC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-B2APVnyk3JaB5toC .node .label{text-align:center;}#mermaid-svg-B2APVnyk3JaB5toC .node.clickable{cursor:pointer;}#mermaid-svg-B2APVnyk3JaB5toC .arrowheadPath{fill:#333333;}#mermaid-svg-B2APVnyk3JaB5toC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-B2APVnyk3JaB5toC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-B2APVnyk3JaB5toC .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-B2APVnyk3JaB5toC .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-B2APVnyk3JaB5toC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-B2APVnyk3JaB5toC .cluster text{fill:#333;}#mermaid-svg-B2APVnyk3JaB5toC .cluster span{color:#333;}#mermaid-svg-B2APVnyk3JaB5toC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-B2APVnyk3JaB5toC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Left Right Left Right Left Right Root Component Left Child 1 Right Child 1 Left Child 2.1 Right Child 2.1 Left Child 2.2 Right Child 2.2 更新过程: #mermaid-svg-Jcr5azPqU0rEFQE9 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Jcr5azPqU0rEFQE9 .error-icon{fill:#552222;}#mermaid-svg-Jcr5azPqU0rEFQE9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Jcr5azPqU0rEFQE9 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-Jcr5azPqU0rEFQE9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Jcr5azPqU0rEFQE9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Jcr5azPqU0rEFQE9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Jcr5azPqU0rEFQE9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Jcr5azPqU0rEFQE9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Jcr5azPqU0rEFQE9 .marker.cross{stroke:#333333;}#mermaid-svg-Jcr5azPqU0rEFQE9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Jcr5azPqU0rEFQE9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Jcr5azPqU0rEFQE9 .cluster-label text{fill:#333;}#mermaid-svg-Jcr5azPqU0rEFQE9 .cluster-label span{color:#333;}#mermaid-svg-Jcr5azPqU0rEFQE9 .label text,#mermaid-svg-Jcr5azPqU0rEFQE9 span{fill:#333;color:#333;}#mermaid-svg-Jcr5azPqU0rEFQE9 .node rect,#mermaid-svg-Jcr5azPqU0rEFQE9 .node circle,#mermaid-svg-Jcr5azPqU0rEFQE9 .node ellipse,#mermaid-svg-Jcr5azPqU0rEFQE9 .node polygon,#mermaid-svg-Jcr5azPqU0rEFQE9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Jcr5azPqU0rEFQE9 .node .label{text-align:center;}#mermaid-svg-Jcr5azPqU0rEFQE9 .node.clickable{cursor:pointer;}#mermaid-svg-Jcr5azPqU0rEFQE9 .arrowheadPath{fill:#333333;}#mermaid-svg-Jcr5azPqU0rEFQE9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Jcr5azPqU0rEFQE9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Jcr5azPqU0rEFQE9 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-Jcr5azPqU0rEFQE9 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-Jcr5azPqU0rEFQE9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Jcr5azPqU0rEFQE9 .cluster text{fill:#333;}#mermaid-svg-Jcr5azPqU0rEFQE9 .cluster span{color:#333;}#mermaid-svg-Jcr5azPqU0rEFQE9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Jcr5azPqU0rEFQE9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Left Right Left Right Left Right Root Component Left Child 1 Right Child 1 Left Child 2.1 Right Child 2.1 Left Child 2.2 Right Child 2.2 在上述更新过程图中我们突出显示了将要被更新的组件节点 - 第二层的右侧子树包括Right Child 1及其所有子节点Left Child 2.2和Right Child 2.2。当setState()被调用时Right Child 1及其所有子组件会被重新渲染从而更新UI。
示例代码
根据根据上面的树结构我们可以创建一个React项目并为每个组件创建一个按钮当按钮被点击时它会更新状态并在控制台输出更新信息。下面是基于该场景的代码示例
import React, { Component } from react;class Node extends Component {constructor(props) {super(props);this.state {updateCount: 0,};}handleUpdate () {this.setState(prevState ({updateCount: prevState.updateCount 1,}), () {console.log(${this.props.name} updated ${this.state.updateCount} times.);});}render() {return (div style{{ border: 1px solid black, padding: 10px, margin: 10px }}button onClick{this.handleUpdate}Update {this.props.name}/button{this.props.children}/div);}
}function App() {return (Node nameRoot ComponentNode nameLeft Child 1Node nameLeft Child 2.1 /Node nameRight Child 2.1 //NodeNode nameRight Child 1Node nameLeft Child 2.2 /Node nameRight Child 2.2 //Node/Node);
}export default App;在这个示例中我们创建了一个Node组件类它包含一个按钮和一个handleUpdate方法。当按钮被点击时handleUpdate方法会被调用它更新updateCount状态并在控制台输出更新信息。App函数组件作为根组件并按照提供的树结构嵌套Node组件。
当你运行这个项目并点击任何一个节点的更新按钮时你会看到对应的节点名和更新次数被打印到控制台中。同时由于React的组件更新机制当你更新一个父节点时它的所有子节点也会被重新渲染。
虚拟DOM和Diff算法是React中用于优化渲染性能的核心技术。通过使用虚拟DOM和Diff算法React能够最小化操作真实DOM的次数从而提高应用的渲染性能。
5. 虚拟DOM (Virtual DOM) 与diff算法
定义虚拟DOM是一个轻量级的对真实DOM的抽象它和真实DOM具有相同的结构但是它存在于内存中而不是真实的浏览器环境中。
目的减少直接操作真实DOM所产生的性能消耗。直接操作真实DOM是非常昂贵的虚拟DOM提供了一种方式使得我们可以在内存中操作DOM然后通过最小的变更来更新真实DOM。
示例
// JSX语法
const element h1Hello, world/h1;// 转化为虚拟DOM对象
const virtualDOM {type: h1,props: {children: Hello, world}
};5.1 Diff算法
定义Diff算法是React用于比较两个虚拟DOM树的差异的算法。
目的找出虚拟DOM树中发生变化的部分以便只更新真实DOM中变化的部分而不是重新渲染整个DOM树。
过程
初次渲染React根据初始的state创建一个虚拟DOM对象树然后根据虚拟DOM生成真实的DOM并渲染到页面中。数据变化当数据变化例如通过setState方法React会重新根据新的数据创建一个新的虚拟DOM对象树。Diff比较React会使用Diff算法比较新旧两个虚拟DOM树找出其中的差异。局部更新根据Diff算法得到的差异React只会更新真实DOM中变化的部分而不是重新渲染整个DOM树。
5.2 实例分析
假设我们有一个列表组件它的内容是根据state中的数据动态生成的。当我们添加一个新的列表项时React会执行以下步骤
创建新的虚拟DOM树React会根据新的state数据创建一个新的虚拟DOM树。执行Diff算法React会比较新旧两个虚拟DOM树找出差异。在这个例子中差异是有一个新的列表项被添加。局部更新真实DOMReact会将新的列表项添加到真实DOM的列表中而不是重新渲染整个列表。
通过这种方式React能够保证只更新真实DOM中变化的部分从而提高渲染性能。
5.3 为什么使用虚拟DOM
我们知道当我们希望实现一个具有复杂状态的界面时如果我们在每个可能发生变化的组件上都绑定事件绑定字段数据那么很快由于状态太多我们需要维护的事件和字段将会越来越多代码也会越来越复杂于是我们想我们可不可以将视图和状态分开来只要视图发生变化对应状态也发生变化然后状态变化我们再重绘整个视图就好了。
这样的想法虽好但是代价太高了于是我们又想能不能只更新状态发生变化的视图于是Virtual Dom应运而生状态变化先反馈到Virtual Dom上Virtual Dom在找到最小更新视图最后批量更新到真实DOM上从而达到性能的提升。
除此之外从移植性上看Virtual Dom还对真实dom做了一次抽象这意味着Virtual Dom对应的可以不是浏览器的DOM而是不同设备的组件极大的方便了多平台的使用。如果是要实现前后端同构直出方案使用Virtual Dom的框架实现起来是比较简单的因为在服务端的Virtual Dom跟浏览器DOM接口并没有绑定关系。
基于 Virtual DOM 的数据更新与UI同步机制:
初始渲染时首先将数据渲染为 Virtual DOM然后由 Virtual DOM 生成 DOM。 数据更新时渲染得到新的 Virtual DOM与上一次得到的 Virtual DOM 进行 diff得到所有需要在 DOM 上进行的变更然后在 patch 过程中应用到 DOM 上实现UI的同步更新。 5.4 总结
虚拟DOM和Diff算法是React优化渲染性能的核心技术。虚拟DOM降低了直接操作真实DOM的性能消耗而Diff算法确保了只更新真实DOM中变化的部分。通过理解和利用这些技术开发者可以创建高性能的React应用提供流畅的用户体验。
虚拟DOM的真正价值从来都不是性能。虚拟DOM的主要价值在于它提供了一种抽象使得开发者可以以声明式的方式描述界面而不需要直接操作DOM。虽然虚拟DOM也有助于提升性能。同时虚拟DOM把DOM虚拟成一个React对象不仅提高了代码的可维护性和可读性还可以使得React的虚拟DOM能够脱离浏览器来运行让能运行js的地方都能运行我们的React。这才是真正虚拟DOM带来的真正价值。
6. React原理机制总结
原理有助于更好地理解React的自身运行机制
了解React的原理如虚拟DOM和Diff算法有助于开发者更好地理解React的运行机制以及为什么React能够提供高性能的渲染。
setState()异步更新数据
React中的setState方法是异步的这意味着在调用setState后state不会立即更新而是在后续的重新渲染过程中更新。这是一个常见的误区但了解这一点可以帮助开发者避免一些常见的问题。
// 示例
this.setState({ count: this.state.count 1 });
console.log(this.state.count); // 输出的是更新前的值父组件更新导致子组件更新纯组件提升性能
当父组件更新时其子组件也会被重新渲染。但如果子组件的props和state没有变化重新渲染是不必要的。使用纯组件PureComponent或shouldComponentUpdate方法可以避免不必要的重新渲染从而提升应用的性能。
// 示例
class MyComponent extends React.PureComponent {render() {return div{this.props.value}/div;}
}思路清晰简单为前提虚拟DOM和Diff保效率
虚拟DOM提供了一种能够简单清晰地描述界面的方式而Diff算法则确保只有必要的部分被更新从而保证了渲染的效率。
虚拟DOM - state JSX
虚拟DOM是通过state和JSX生成的它为开发者提供了一种声明式的方式来描述界面使得代码更易于理解和维护。
虚拟DOM的真正价值从来都不是性能
虚拟DOM的主要价值在于它提供了一种抽象使得开发者可以以声明式的方式描述界面而不需要直接操作DOM。虽然虚拟DOM也有助于提升性能。同时虚拟DOM把DOM虚拟成一个React对象不仅提高了代码的可维护性和可读性还可以使得React的虚拟DOM能够脱离浏览器来运行让能运行js的地方都能运行我们的React。这才是真正虚拟DOM带来的真正价值。
结论
React通过其独特的虚拟DOM和Diff算法以及其组件模型和生命周期方法为开发者提供了一种高效、简洁和可维护的方式来构建用户界面。通过深入理解React的原理和运行机制开发者可以更好地利用React的优势构建高性能和可维护的应用。