[TOC]
-
什么是Dom
DOM是用一颗逻辑树来表示一个文档,树的每个分支的终点都是一个节点,可以用特定的方式(编写JS、CSS、HTML)来改变这个树的结构,从而改变文档结构、样式或内容。
-
什么是虚拟Dom
虚拟DOM就是一个JS对象,通过对象的方式来表示DOM结构,通过事务处理机制,将多次DOM修改的结果一次性更新到页面上,从而有效的减少页面渲染次数,减少修改DOM重绘重排的时间,提高渲染性能。
React在内存中维护一个跟真实DOM一样的虚拟DOM树,再改动完组件后,会再生成一个新的虚拟DOM,React会将新的虚拟DOM和原来的虚拟DOM进行对比,找出两个DOM树的不同的地方(diff),然后在真实DOM上更新diff,提高渲染速度。
-
为什么要使用虚拟dom
- 提供更好的性能
- 对于真实DOM:生成HTML字符串,重建
所有
DOM元素 - 对于虚拟DOM:生成虚拟DOM节点,采用diff算法,更新出现变化的真实DOM节点
- 对于真实DOM:生成HTML字符串,重建
- 虚拟DOM虽然要进行更多的步骤,但它的性能消耗是极低的。
- 提供更好的性能
-
使用jsx创建虚拟dom(推荐使用)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建虚拟dom const Vdom = ( <h1 id="title"><span>Hello,React</span></h1> )//此处不要写引号,因为不是字符串 //2.渲染虚拟dom到页面 ReactDOM.render(Vdom,document.getElementById('test')) console.log(Vdom) </script> </body> </html>
-
使用js创建虚拟dom(不推荐使用太繁琐)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script type="text/javascript"> //1.创建虚拟dom const Vdom = React.createElement('h1',{ id:'title' },React.createElement('span',{},'Hello,React')) //2.渲染虚拟dom到页面 ReactDOM.render(Vdom,document.getElementById('test')) </script> </body> </html>
-
什么是虚拟dom
-
总结:jsx创建虚拟dom就是原始js创建虚拟dom的语法糖
-
全称:JavaScript XML
-
React定义的一种类似于XML的JS扩展语法:JS + XML
-
本质是React.createElement(component,props,...children)方法的语法糖
-
作用:用来简化创建虚拟Dom
-
定义虚拟dom时不要写引号
-
标签中混入JS表达式时要用{}
-
样式的类名指定不要用class,要用className
-
内联样式要用style = {{key:value}}的方式书写
-
只有一个根标签
-
标签必须闭合
-
标签首字母
-
若小写字母开头,则将该标签转为html中同名的元素,若html中无该标签对应的同名元素,则会报错
-
若大写字母开头,则react就去渲染对应的组件,若组件没有定义,则报错
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .title{ background-color: red; width: 200px; } </style> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> const myId = 'myId' const myData = 'Hello,React' //1.创建虚拟dom const VDOM = ( <div> <h2 className='title' id={myId}> <span style={{color:'white',fontSize:'29px'}}> {myData.toLocaleLowerCase()} </span> </h2> <input type="text"/> </div> ) //2.渲染虚拟dom到页面 ReactDOM.render(VDOM,document.getElementById('test')) </script> </body> </html>
-
- 模块
- 理解:向外提供特定功能的js程序,一般就是一个js文件
- 为什么要拆成模块:随着业务逻辑的增加,代码越来越多且复杂
- 作用:复用js,简化js的编写,提高js运行效率
- 组件
- 理解:用来实现局部功能效果的代码和资源的集合(html,css,js,image等等)
- 为什么L一个界面的功能更复杂的情况下往往需要拆分成不同的组件
- 作用:复用代码,简化项目编码,提高运行效率
- 模块化
- 当应用的js都以模块来编写的,这个应用就是一个模块化的应用
- 组件化
- 当应用是以多个组件的方式实现的情况下,这个应用就是一个组件化的应用
-
类式组件必须具有的条件
- 类式组件必须继承React.Component
- 类式组件必须有render函数
- 类式组件render必须有返回值
-
代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class MyComponent extends React.Component{ //render是放在哪里的? ---MyComponent类的原型对象上,供实列使用 //render中的this是谁? --- MyComponent的实列对象。MyComponent组件实列对象 render(){ return ( <h1>我是类式组件</h1> ) } } //渲染组件到页面 ReactDOM.render(<MyComponent/>,document.getElementById('test')) /* 执行ReactDOM,render(<MyCompnent/>,..)之后发生了什么? 1.React解析组件标签,找到了MyCompnent组件 2.发现组件是使用类定义的,随后new出来该类的实列,并通过实列调用到原型上的render方法 3.将render返回的虚拟DOM转为真实的DOM,随后呈现在页面中 */ </script> </body> </html>
-
理解
- state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- 组件被称为状态机,通过更新组件的state来更新对应的页面显示(重新渲染组件)
-
state的使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class Weather extends React.Component{ //构造器只调用一次 constructor(props){ super(props)//调用父类构造器 this.state = {//初始化状态 isHot:true, wind:'微风' } } // changeWeather(){ // /* // 由于changeWeather为onClick的回调,所以不是通过 // 实列调用的,是直接调用 // 类中的方法默认开启了局部严格模式,所以changeWeather // 中的this为undefind // */ // } //如何解决这个问题(使用箭头函数)或者this.changeWeather = this.changeWeather.bind(this) changeWeather = () =>{ //严重注意:状态state不可以直接更改,需要借助内置的api去更改 //状态需要使用setState进行更改 this.setState({isHot:!this.state.isHot}) } //render调用几次?--->1+n次 //changeWeather执行几次 ->点几次执行几次 render(){ //读取状态 const {isHot} = this.state return ( <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1> ) } } //渲染组件到页面 ReactDOM.render(<Weather/>,document.getElementById('test')) </script> </body> </html>
-
state的简化操作
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class Weather extends React.Component{ state = { isHot:true } changeWeather = () =>{ this.setState({isHot:!this.state.isHot}) } render(){ const {isHot} = this.state return ( <h1 onClick={this.changeWeather}> 今天天气很{isHot ? '炎热' : '凉爽'} </h1> ) } } //渲染组件到页面 ReactDOM.render(<Weather/>,document.getElementById('test')) </script> </body> </html>
-
总结
- 组件中render方法中的this为组件实列对象
- 组件自定义的方法中this为undefined如何解决?
- 强制绑定this,通过函数对象的bind()
- 箭头函数
- 状态数据,不能直接修改或更新
-
如何在类式组件中使用props
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test1"></div> <div id="test2"></div> <div id="test3"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class Person extends React.Component{ render(){ const {name,age,sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } } //渲染组件到页面 const person = { name:'老刘', age:35, sex:'男' } let person1 = {...person,name:'老王'} //如果属性过多的情况下可以 ReactDOM.render(<Person {...person}/>,document.getElementById('test3')) ReactDOM.render(<Person {...person1} />,document.getElementById('test1')) ReactDOM.render(<Person name="AL" age="18" sex="男"/>,document.getElementById('test2')) </script> </body> </html>
-
如何使用propType验证props的数据类型
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test1"></div> <div id="test2"></div> <div id="test3"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <!-- 引入proptype.js用于验证props类型 --> <script crossorigin src="https://unpkg.com/[email protected]/prop-types.js"></script> <script type="text/babel"> //1.创建类式组件 class Person extends React.Component{ render(){ const {name,age,sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } } //对标签进行类型,必要性限制 Person.propTypes = { name:PropTypes.string.isRequired,//名字必须是string类型而且必须是必传 age:PropTypes.number,//性别必须是number类型(非必传) sex:PropTypes.string,//性别必须为string类型(非必传) speak:PropTypes.func//方法必须为一个function(非必传) } //指定默认值 Person.defaultProps = { sex:'男', age:18 } //渲染组件到页面 const person = { name:'老刘', age:35, sex:'男' } let person1 = {...person,name:'老王'}//修改 function speak(){ console.log('我说话了') } //如果属性过多的情况下可以 ReactDOM.render(<Person {...person}/>,document.getElementById('test3')) ReactDOM.render(<Person speak={speak} name="wmq" age={99} sex="女" />,document.getElementById('test1')) ReactDOM.render(<Person name="AL" age={18} sex="男"/>,document.getElementById('test2')) </script> </body> </html>
-
类式组件中prpType的简写形式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test1"></div> <div id="test2"></div> <div id="test3"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <!-- 引入proptype.js用于验证props类型 --> <script crossorigin src="https://unpkg.com/[email protected]/prop-types.js"></script> <script type="text/babel"> //1.创建类式组件 class Person extends React.Component{ //对标签属性进行类型,必要性的限制 static propTypes = { name:PropTypes.string.isRequired,//名字必须是string类型而且必须是必传 age:PropTypes.number,//性别必须是number类型(非必传) sex:PropTypes.string,//性别必须为string类型(非必传) speak:PropTypes.func//方法必须为一个function(非必传) } //指定默认标签属性值 static defaultProps = { sex:'男', age:18 } render(){ const {name,age,sex} = this.props //props是只读的不能修改 return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } } //渲染组件到页面 const person = { name:'老刘', age:35, sex:'男' } let person1 = {...person,name:'老王'}//修改 function speak(){ console.log('我说话了') } //如果属性过多的情况下可以 ReactDOM.render(<Person {...person}/>,document.getElementById('test3')) ReactDOM.render(<Person speak={speak} name="wmq" age={99} sex="女" />,document.getElementById('test1')) ReactDOM.render(<Person name="AL" age={18} sex="男"/>,document.getElementById('test2')) </script> </body> </html>
-
类式组件中的props和构造器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <!-- 引入proptype.js用于验证props类型 --> <script crossorigin src="https://unpkg.com/[email protected]/prop-types.js"></script> <script type="text/babel"> //1.创建类式组件 class Person extends React.Component{ /* 通常在React中构造函数仅用于以下两种情况 1.通过this.state赋值对象来初始化内部state 2.为事件处理函数绑定实列 注意:在构造器中不要调用setState()方法,如果你的组件需要使用内部的state 轻直接在构造函数中为this.state赋初始state */ constructor(props){ //构造器是否接受props,是否传递给super取决于是否希望在构造器中通过this访问props super(props) } //对标签属性进行类型,必要性的限制 static propTypes = { name:PropTypes.string.isRequired,//名字必须是string类型而且必须是必传 age:PropTypes.number,//性别必须是number类型(非必传) sex:PropTypes.string,//性别必须为string类型(非必传) } //指定默认标签属性值 static defaultProps = { sex:'男', age:18 } render(){ const {name,age,sex} = this.props //props是只读的不能修改 return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } } //渲染组件到页面 ReactDOM.render(<Person name="老王"/>,document.getElementById('test')) </script> </body> </html>
-
函数组件中props的使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <!-- 引入proptype.js用于验证props类型 --> <script crossorigin src="https://unpkg.com/[email protected]/prop-types.js"></script> <script type="text/babel"> //1.创建函数式组件 function Person(props){ const {name,age,sex} = props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } //对标签属性进行类型,必要性的限制 Person.propTypes = { name:PropTypes.string.isRequired,//名字必须是string类型而且必须是必传 age:PropTypes.number,//性别必须是number类型(非必传) sex:PropTypes.string,//性别必须为string类型(非必传) speak:PropTypes.func//方法必须为一个function(非必传) } //指定默认标签属性值 Person.defaultProps = { sex:'男', age:18 } //渲染组件到页面 ReactDOM.render(<Person name="wmq" age={18} sex="男"/>,document.getElementById('test')) </script> </body> </html>
-
字符串形式的ref
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class MyComponent extends React.Component{ showData = () =>{ const {input1} = this.refs alert(input1.value) } showData2 = () =>{ const {input2} = this.refs alert(input2.value) } render(){ return ( <div> <input ref="input1" type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧数据</button> <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<MyComponent/>,document.getElementById('test')) </script> </body> </html>
这种字符串类型的refs已经过时,我们不建议使用它,因为 string 类型的 refs 存在 一些问题。它已过时并可能会在未来的版本被移除
-
回调形式的ref
-
回调形式的ref
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class MyComponent extends React.Component{ showData = () =>{ console.log(this.input1.value) } render(){ return ( <div> <input ref={c =>this.input1 = c} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧数据</button> </div> ) } } ReactDOM.render(<MyComponent/>,document.getElementById('test')) </script> </body> </html>
问题:当页面渲染时(除去第一次渲染)该回调会被执行两次,第一次为null,第二次为最新的dom节点。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。(但是对性能影响不大)开发中常用
-
class 的绑定函数的方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class MyComponent extends React.Component{ state = { isHot:true } showData = () =>{ console.log(this.input1.value) } saveInput = (c) =>{ this.input1 = c console.log('@',c) } changeWeather = () =>{ const {isHot} = this.state this.setState({isHot:!isHot}) } render(){ const {isHot} = this.state return ( <div> <input ref={this.saveInput} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧数据</button> <h1>今天天气很{isHot ? '炎热' : '凉爽'}</h1> <button onClick={this.changeWeather}>点击修改天气</button> </div> ) } } ReactDOM.render(<MyComponent/>,document.getElementById('test')) </script> </body> </html>
使用类绑定模式只会在第一次渲染时执行回调
-
-
createRef容器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class MyComponent extends React.Component{ /* React.createRef()调用后可以返回一个容器可以存储被ref标识的节点 该容器是专人专用的只能存一个 */ myRef = React.createRef() myRef2 = React.createRef() showData = () =>{ console.log(this.myRef.current.value) } inputBlur = () =>{ console.log(this.myRef2.current.value) } render(){ return ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧数据</button> <input ref={this.myRef2} onBlur = {this.inputBlur} type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<MyComponent/>,document.getElementById('test')) </script> </body> </html>
这是react最推荐的一种写法
-
注意
- 你不能在函数组件上使用
ref
属性,因为他们没有实例 - 当
ref
属性用于自定义 class 组件时,ref
对象接收组件的挂载实例作为其current
属性 - 当
ref
属性用于 HTML 元素时,构造函数中使用React.createRef()
创建的ref
接收底层 DOM 元素作为其current
属性 - 请勿过度使用ref
- 你不能在函数组件上使用
-
事件处理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> /* react中的事件处理 1.通过onXxx属性指定事件处理函数(注意大小写) a.React使用的是自定义(合成)事件,而不是使用的原生Dom事件 ---为了更好的兼容性 b.React中的事件是通过事件委托方式处理的(委托给组件最外层元素)---事件委托的方式效率更高 2.通过Event.target得到发生事件的Dom元素对象---- 不要过度使用ref(发生事件的元素正好是你要操作的元素就可以使用事件委托) */ //1.创建类式组件 class MyComponent extends React.Component{ //创建ref容器 myRef = React.createRef() myRef2 = React.createRef() showData = () =>{ console.log(this.myRef.current.value) } inputBlur = (event) =>{//发生事件的元素正好是你要操作的元素就可以使用事件委托 console.log(event.target.value) } render(){ return ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧数据</button> <input onBlur = {this.inputBlur} type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<MyComponent/>,document.getElementById('test')) </script> </body> </html>
-
非受控组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.非受控组件(现用现取的组件) class Login extends React.Component{ handleSubmit = (event) =>{ event.preventDefault()//阻止表单提交 const {username,password} = this console.log(username.value,password.value) } render(){ return ( <form action="http://www.atguigu.com" onSubmit={this.handleSubmit}> 用户名:<input ref={c =>this.username = c} type="text" name="username"/> 密码:<input ref={c =>this.password = c} type="password"name="password" /> <button>登录</button> </form> ) } } ReactDOM.render(<Login/>,document.getElementById('test')) </script> </body> </html>
-
受控组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.受控组件(随着输入就可以维护到状态中,使用的话就可以取出来) class Login extends React.Component{ state = { username:'', password:'' } handleSubmit = (event) =>{ event.preventDefault() const {username,password} = this.state alert(`你的账号为${username},密码为${password}`) } usernameChange = (event) =>{ this.setState({ username:event.target.value }) } passwordChange = () =>{ this.setState({ password:event.target.value }) } render(){ return ( <form action="http://www.atguigu.com" onSubmit={this.handleSubmit}> 用户名:<input onChange={this.usernameChange} type="text" name="username"/> 密码:<input onChange={this.passwordChange} type="password"name="password" /> <button>登录</button> </form> ) } } ReactDOM.render(<Login/>,document.getElementById('test')) </script> </body> </html>
-
引出生命周期
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class Life extends React.Component{ state = { opacity:0.5, } //组件挂载完毕调用(只触发一次) componentDidMount(){ let {opacity} = this.state this.timer = setInterval(() =>{ opacity -= 0.1 if(opacity<=0) opacity = 1 this.setState({opacity}) },200) } //组件将要卸载时调用 componentWillUnmount(){ //清除定时器 clearInterval(this.timer) } death = () =>{ //卸载组件 ReactDOM.unmountComponentAtNode(document.getElementById('test')) } render(){ const {opacity} = this.state return ( <div> <h2 style={{opacity}} >React学不会怎么办?</h2> <button onClick={this.death}>不活了</button> </div> ) } } //渲染组件到页面 ReactDOM.render(<Life/>,document.getElementById('test')) </script> </body> </html>
-
组件挂载流程
-
旧版本流程图
-
组件初始化生命周期过程
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class Count extends React.Component{ constructor(props){ console.log('count---constructor') super(props) this.state = { count:0 } } //组件将要挂载时调用 componentWillMount(){ console.log('count---componentWillMount') } //组件挂载完毕调用 componentDidMount(){ console.log('count---componentDidMount') } //组件将要卸载时调用 componentWillUnmount(){ console.log('count---componentWillUnmount') } handleAddNum = () =>{ let {count} = this.state this.setState({ count:count+1 }) } render(){ console.log('count---render') const {count} = this.state return ( <div> <h1>当前求和为:{count}</h1> <button onClick={this.handleAddNum}>点我+1</button> </div> ) } } //渲染组件到页面 ReactDOM.render(<Count/>,document.getElementById('test')) </script> </body> </html>
总结:组件渲染时生命周期过程为constructor->componentWillMount->render->componentDidMount
-
组件卸载过程
总结:componentWillUnmount ->unmountComponentAtNode
-
组件状态更新过程
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class Count extends React.Component{ constructor(props){ console.log('count---constructor') super(props) this.state = { count:0 } } //组件将要挂载时调用 componentWillMount(){ console.log('count---componentWillMount') } //组件挂载完毕调用 componentDidMount(){ console.log('count---componentDidMount') } //组件将要卸载时调用 componentWillUnmount(){ console.log('count---componentWillUnmount') } //判断组件是否需要更新默认返回true需要更新,返回false则不会更新 shouldComponentUpdate(){ console.log('count---shouldComponentUpdate') return true } //组件将要更新 componentWillUpdate(){ console.log('count-componentWillUpdate') } //组件更新完毕 componentDidUpdate(){ console.log('count---componentDidUpdate') } handleAddNum = () =>{ let {count} = this.state this.setState({ count:count+1 }) } death = () =>{ //卸载组件 ReactDOM.unmountComponentAtNode(document.getElementById('test')) console.log('count---unmountComponentAtNode') } render(){ console.log('count---render') const {count} = this.state return ( <div> <h1>当前求和为:{count}</h1> <button onClick={this.handleAddNum}>点我+1</button> <button onClick={this.death}>卸载组件</button> </div> ) } } //渲染组件到页面 ReactDOM.render(<Count/>,document.getElementById('test')) </script> </body> </html>
组件更新流程为
shouldComponentUpdate ->componentWillUpdate->render->componentDidUpdate\
-
强制更新流程
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class Count extends React.Component{ constructor(props){ console.log('count---constructor') super(props) this.state = { count:0 } } //组件将要挂载时调用 componentWillMount(){ console.log('count---componentWillMount') } //组件挂载完毕调用 componentDidMount(){ console.log('count---componentDidMount') } //组件将要卸载时调用 componentWillUnmount(){ console.log('count---componentWillUnmount') } //判断组件是否需要更新默认返回true需要更新,返回false则不会更新 shouldComponentUpdate(){ console.log('count---shouldComponentUpdate') return false } //组件将要更新 componentWillUpdate(){ console.log('count-componentWillUpdate') } //组件更新完毕 componentDidUpdate(){ console.log('count---componentDidUpdate') } handleAddNum = () =>{ let {count} = this.state this.setState({ count:count+1 }) } //卸载组件 death = () =>{ ReactDOM.unmountComponentAtNode(document.getElementById('test')) console.log('count---unmountComponentAtNode') } //强制更新按钮回调 force = () =>{ this.forceUpdate() } render(){ console.log('count---render') const {count} = this.state return ( <div> <h1>当前求和为:{count}</h1> <button onClick={this.handleAddNum}>点我+1</button> <button onClick={this.death}>卸载组件</button> <button onClick={this.force}>不更改任何状态数据强制更新</button> </div> ) } } //渲染组件到页面 ReactDOM.render(<Count/>,document.getElementById('test')) </script> </body> </html>
总结:强制更新流程forceUpdate ->componentWillUpdate->-render->componentDidUpdate
-
父组件render流程
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建组件 class Father extends React.Component{ state = { carName:'奔驰' } changeCar = () =>{ this.setState({carName:'奥拓'}) } render(){ return ( <div> <div>我是Father组件</div> <button onClick={this.changeCar}>换车</button> <Child carName={this.state.carName}/> </div> ) } } class Child extends React.Component{ //组件将要接受新的props componentWillReceiveProps(props){ console.log('Child---componentWillReceiveProps',props) } //控制组件更新的阀门 shouldComponentUpdate(){ console.log('Child---shouldComponentUpdate') return true } //组件将要更新 componentWillUpdate(){ console.log('Child-componentWillUpdate') } //组件更新完毕 componentDidUpdate(){ console.log('Child---componentDidUpdate') } render(){ const {carName} = this.props return ( <div>我是Child组件,我的车为{carName}</div> ) } } //渲染组件到页面 ReactDOM.render(<Father/>,document.getElementById('test')) </script> </body> </html>
总结:当父组件初次渲染时并不会触发子组件componentWillReceiveProps方法当父组件传递给子组件props变化时,会调用子组件的更新。执行过程为:
componentWillReceiveProps->shouldComponentUpdate->componentWillUpdate->componentDidUpdate
-
总结
-
初始化阶段:由ReactDOM.rnder()触发一次渲染
-
constructor()
-
componentWillMount()
-
render()
-
componentDidMount()
常用:一般在这钩子中做一些初始化的事,列如:开启定时器,发送网络请求,订阅消息
-
-
更新阶段:由组件内部this.setState()或父组件更新render()触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
-
卸载组件:由ReactDOM.unmountComponentAtNode()触发
-
componentWillUnmount()
一般在这个钩子中做一些收尾的事,列如:关闭定时器,取消订阅消息
-
-
-
-
生命周期图
-
getDerivedStateFormProps的使用(极低)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <!-- 准备好一个容器 --> <div id="test"></div> <!-- react核心库 --> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 引入react-dom,用于支持react操作Dom --> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 用于将jsx转化为js --> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> //1.创建类式组件 class Count extends React.Component{ constructor(props){ console.log('count---constructor') super(props) this.state = { count:0 } } //若state的值任何时候都取决于props可以使用getDerivedStateFromProps static getDerivedStateFromProps(props,state){ console.log('getDerivedStateFromProps',props,state) return null } //组件挂载完毕调用 componentDidMount(){ console.log('count---componentDidMount') } //组件将要卸载时调用 componentWillUnmount(){ console.log('count---componentWillUnmount') } //判断组件是否需要更新默认返回true需要更新,返回false则不会更新 shouldComponentUpdate(){ console.log('count---shouldComponentUpdate') return true } //组件更新完毕 componentDidUpdate(){ console.log('count---componentDidUpdate') } handleAddNum = () =>{ let {count} = this.state this.setState({ count:count+1 }) } death = () =>{ //卸载组件 ReactDOM.unmountComponentAtNode(document.getElementById('test')) console.log('count---unmountComponentAtNode') } render(){ console.log('count---render') const {count} = this.state return ( <div> <h1>当前求和为:{count}</h1> <button onClick={this.handleAddNum}>点我+1</button> <button onClick={this.death}>卸载组件</button> </div> ) } } //渲染组件到页面 ReactDOM.render(<Count count={199}/>,document.getElementById('test')) </script> </body> </html>
-
getSnapshotBeforeUpdate的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- react核心库 -->
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作Dom -->
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<!-- 用于将jsx转化为js -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
//1.创建类式组件
class Count extends React.Component{
constructor(props){
console.log('count---constructor')
super(props)
this.state = {
count:0
}
}
//若state的值任何时候都取决于props可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state)
return null
}
//组件挂载完毕调用
componentDidMount(){
console.log('count---componentDidMount')
}
//组件将要卸载时调用
componentWillUnmount(){
console.log('count---componentWillUnmount')
}
//判断组件是否需要更新默认返回true需要更新,返回false则不会更新
shouldComponentUpdate(){
console.log('count---shouldComponentUpdate')
return true
}
//在更新之前获取快照
getSnapshotBeforeUpdate(state){
console.log('count---getSnapshotBeforeUpdate')
return 'atguigu'//此处返回的值会在componentDidUpdate第三个参数中接收
}
//组件更新完毕
componentDidUpdate(preProps,preState,snapshotValue){
console.log('count---componentDidUpdate',preProps,preState,snapshotValue)
}
handleAddNum = () =>{
let {count} = this.state
this.setState({
count:count+1
})
}
death = () =>{
//卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
console.log('count---unmountComponentAtNode')
}
render(){
console.log('count---render')
const {count} = this.state
return (
<div>
<h1>当前求和为:{count}</h1>
<button onClick={this.handleAddNum}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Count count={199}/>,document.getElementById('test'))
</script>
</body>
</html>
实际应用场景
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.list{
width: 200px;
height: 150px;
background-color:black;
overflow: auto;
}
.news{
width: 100%;
border-bottom: 1px solid white;
line-height: 30px;
height: 30px;
color: white;
font-size: 13px;
}
</style>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- react核心库 -->
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作Dom -->
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<!-- 用于将jsx转化为js -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
//1.创建类式组件
class NewsList extends React.Component{
state = {
newsArr:[]
}
conRef = React.createRef()
componentDidMount(){
setInterval(() =>{
const {newsArr} = this.state
const news = `新闻${newsArr.length+1}`
this.setState({
newsArr:[news,...newsArr]
})
},1000)
}
getSnapshotBeforeUpdate(){
return this.conRef.current.scrollHeight
}
componentDidUpdate(preProps,preState,height){
const distance = this.conRef.current.scrollHeight - height
this.conRef.current.scrollTop += distance
}
render(){
return (
<div ref={this.conRef} className="list">
{
this.state.newsArr.map((item,index) =>{
return <div key={index} className="news">{item}</div>
})
}
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<NewsList />,document.getElementById('test'))
</script>
</body>
</html>
总结
-
新版本生命周期废除了旧版本三个生命周期componentWillMount,componentWillUpdate,componentWillReceiveProps.若在新版本中使用需要加UNSAFE_的前缀
-
新版本的初始化过程为:由React.render()触发---初次渲染
constructor() ->getDerivedStateFromProps()->render()->componentDidMount
-
新版本更新过程:由组件内部this.setState()或父组件重新render触发
getDerivedStateFromProps()->shouldComponentUpdate()->render()->getSnapshotBeforeUpdate()->getSnapshotBeforeUpdate()
-
卸载过程:由ReactDOM.unmountComponentAtNode()触发
1.componentWillUnmount()常用->一般在这个钩子里面做一些收尾的事,l比如关闭定时器,取消订阅消息\
-
重要的勾子
- render() 初始化渲染或更新渲染调用
- componentDidMound()->开启监听发送请求
- componentWillUnmount()->做一些收尾的事,l比如关闭定时器,取消订阅消息
-
即将废弃的钩子
-
componentWillMount()
-
componentWillUpdate()
-
componentWillReceiveProps()
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会彻底废弃不建议使用
-
- 全局安装:npm install -g create-react-app
- 切换到你想创建项目的目录,使用命令:create-react-app xx (项目名称)
-
public文件夹下一般放静态资源(图片,图标,css等)
-
src文件夹一般放项目源代码
-
执行npm i [email protected] 下载路由插件
-
在组件中使用路由
import React, { Component } from 'react' import { NavLink, Route, Switch } from 'react-router-dom' import AppStyle from './App.module.css' import About from './component/abount/index' import Home from './component/home/index' export default class App extends Component { render() { return ( <div style={{display:'flex',padding:'100px'}}> <div className={AppStyle.left}> <NavLink to='/home' className="btn btn-primary active">Home</NavLink> <NavLink to='/about' style={{marginTop:'10px'}} className="btn btn-primary">About</NavLink> </div> <div className={AppStyle.right}> <Switch> <Route path='/home' component={Home}/> <Route path='/about' component={About}/> </Switch> </div> </div> ) } }
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from 'react-router-dom'; import App from './App'; ReactDOM.render( <BrowserRouter> <App/> </BrowserRouter> ,document.getElementById('root'))
路由使用必须在使用组件范围内包裹BrowserRouter或HashRouter
navLink组件中className选中默认会出现active类类名,该类名也可通过属性activeClassName进行修改
Switch组件匹配path以后就不会匹配相同的路一个路径只匹配一次,可以调高效率
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom/cjs/react-router-dom.min'
export default class MyNavLink extends Component {
render() {
return (
<NavLink className="btn btn-primary" {...this.props}/>
)
}
}
-
模糊匹配
import React, { Component } from 'react' import { Route, Switch } from 'react-router-dom' import { Redirect } from 'react-router-dom/cjs/react-router-dom.min' import AppStyle from './App.module.css' import About from './component/abount/index' import Home from './component/home/index' import MyNavLink from './component/mylink' export default class App extends Component { render() { return ( <div style={{display:'flex',padding:'100px'}}> <div className={AppStyle.left}> <MyNavLink to='/home/aaa'>Home</MyNavLink> <MyNavLink style={{marginTop:'10px'}} to='/about'>About</MyNavLink> </div> <div className={AppStyle.right}> <Switch> <Route path='/home' component={Home}/> <Route path='/about' component={About}/> </Switch> </div> </div> ) } }
默认使用的是模糊匹配(简单记:{输入的路径}必须要包含{匹配的路径},且顺序要一致)
-
严格匹配
import React, { Component } from 'react' import { Route, Switch } from 'react-router-dom' import { Redirect } from 'react-router-dom/cjs/react-router-dom.min' import AppStyle from './App.module.css' import About from './component/abount/index' import Home from './component/home/index' import MyNavLink from './component/mylink' export default class App extends Component { render() { return ( <div style={{display:'flex',padding:'100px'}}> <div className={AppStyle.left}> <MyNavLink to='/home/aaa'>Home</MyNavLink> <MyNavLink style={{marginTop:'10px'}} to='/about'>About</MyNavLink> </div> <div className={AppStyle.right}> <Switch> <Route exact path='/home' component={Home}/> <Route exact path='/about' component={About}/> </Switch> </div> </div> ) } }
exact={true}开启严格匹配
严格匹配不要随便开启,需要时候再打开,有些时候开启会导致无法继续匹配二级路由.(使用的时候需要慎重考虑是否符合自己的需求)
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import { Redirect } from 'react-router-dom/cjs/react-router-dom.min'
import AppStyle from './App.module.css'
import About from './component/abount/index'
import Home from './component/home/index'
import MyNavLink from './component/mylink'
export default class App extends Component {
render() {
return (
<div style={{display:'flex',padding:'100px'}}>
<div className={AppStyle.left}>
<MyNavLink to='/home/aaa'>Home</MyNavLink>
<MyNavLink style={{marginTop:'10px'}} to='/about'>About</MyNavLink>
</div>
<div className={AppStyle.right}>
<Switch>
<Route path='/home' component={Home}/>
<Route path='/about' component={About}/>
<Redirect to='/home'/>
</Switch>
</div>
</div>
)
}
}
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import { Redirect } from 'react-router-dom/cjs/react-router-dom.min'
import AppStyle from './App.module.css'
import About from './component/abount/index'
import Home from './component/home/index'
import MyNavLink from './component/mylink'
export default class App extends Component {
render() {
return (
<div style={{display:'flex',padding:'100px'}}>
<div className={AppStyle.left}>
<MyNavLink to='/home/aaa'>Home</MyNavLink>
<MyNavLink style={{marginTop:'10px'}} to='/about'>About</MyNavLink>
</div>
<div className={AppStyle.right}>
<Switch>
<Route path='/home' component={Home}/>
<Route path='/about' component={About}/>
<Redirect to='/home'/>
</Switch>
</div>
</div>
)
}
}
import React, { Component } from 'react'
import { Redirect, Route, Switch } from 'react-router-dom'
import Message from '../message/index'
import MyNavLink from '../mylink/index'
import News from '../news/index'
export default class Home extends Component {
render() {
return (
<div>
<div className="card">
<div className="card-body">
<div className="btn-group" role="group" aria-label="Basic mixed styles example" style={{marginBottom:'10px'}}>
<MyNavLink to='/home/news'>news</MyNavLink>
<MyNavLink to='/home/message' style={{marginLeft:'3px'}}>message</MyNavLink>
</div>
<div>
<Switch>
<Route path='/home/news' component={News}/>
<Route path='/home/message' component={Message}/>
<Redirect to='/home/news'/>
</Switch>
</div>
</div>
</div>
</div>
)
}
}
-
传递params参数
import React, { Component } from 'react' import { Link, Route } from 'react-router-dom' import Detail from './detail' export default class Message extends Component { state = { messageArr:[ { id:'01', title:'消息1' }, { id:'02', title:'消息2' }, { id:'03', title:'消息3' }, { id:'04', title:'消息4' } ] } render() { const {messageArr} = this.state return ( <div> <ul className="list-group"> { messageArr.map(msg =>{ return ( // {向路由组件传递params参数} <Link key={msg.id} to={`/home/message/detail/${msg.id}/${msg.title}`} className="list-group-item">{msg.title}</Link> ) }) } </ul> <hr/> {/* {声明接受params参数} */} <Route path="/home/message/detail/:id/:title" component={Detail}/> </div> ) } }
import React, { Component } from 'react' const data = [ {id:'01',content:'你好中国1'}, {id:'02',content:'你好中国2'}, {id:'03',content:'你好中国3'}, {id:'04',content:'你好中国4'} ] export default class Detail extends Component { render() { console.log(this.props) const {id,title} = this.props.match.params const findRes = data.find(it =>it.id === id) return ( <div> <ul> <li>{id}</li> <li>{title}</li> <li>{findRes.content}</li> </ul> </div> ) } }
总结:
- 路由链接(携带参数):详情
- 注册路由(声明参数):
- 接收参数:const {id} = this.props.match.params
-
传递search参数
import React, { Component } from 'react' import { Link, Route } from 'react-router-dom' import Detail from './detail' export default class Message extends Component { state = { messageArr:[ { id:'01', title:'消息1' }, { id:'02', title:'消息2' }, { id:'03', title:'消息3' }, { id:'04', title:'消息4' } ] } render() { const {messageArr} = this.state return ( <div> <ul className="list-group"> { messageArr.map(msg =>{ return ( // {像路由传递search参数} <Link key={msg.id} to={`/home/message/detail/?id=${msg.id}&title=${msg.title}`} className="list-group-item">{msg.title}</Link> ) }) } </ul> <hr/> {/* {声明接收search参数(search参数无须声明接收)} */} <Route path="/home/message/detail" component={Detail}/> </div> ) } }
import qs from 'qs' import React, { Component } from 'react' const data = [ {id:'01',content:'你好中国1'}, {id:'02',content:'你好中国2'}, {id:'03',content:'你好中国3'}, {id:'04',content:'你好中国4'} ] export default class Detail extends Component { render() { // {接收search参数} const {search} = this.props.location const {id,title} = qs.parse(search.slice(1)) const findRes = data.find(it =>it.id === id) return ( <div> <ul> <li>{id}</li> <li>{title}</li> <li>{findRes.content}</li> </ul> </div> ) } }
总结
-
路由链接(携带参数):详情
-
注册路由(无需声明,正常注册即可):
-
接收参数:const {search} = this.props.location
备注:获取到的search是urlencoded编码的字符串需要借助qs进行解析:const {id} = qs.parse(search.slice(1))
-
-
传递state参数
import React, { Component } from 'react' import { Link, Route } from 'react-router-dom' import Detail from './detail' export default class Message extends Component { state = { messageArr:[ { id:'01', title:'消息1' }, { id:'02', title:'消息2' }, { id:'03', title:'消息3' }, { id:'04', title:'消息4' } ] } render() { const {messageArr} = this.state return ( <div> <ul className="list-group"> { messageArr.map(msg =>{ return ( // {向路由传递state参数} <Link key={msg.id} to={{pathname:'/home/message/detail',state:msg}} className="list-group-item">{msg.title}</Link> ) }) } </ul> <hr/> {/* {state参数无需接收} */} <Route path="/home/message/detail" component={Detail}/> </div> ) } }
import React, { Component } from 'react' const data = [ {id:'01',content:'你好中国1'}, {id:'02',content:'你好中国2'}, {id:'03',content:'你好中国3'}, {id:'04',content:'你好中国4'} ] export default class Detail extends Component { render() { // {接收state参数} const {id,title} = this.props.location.state const findRes = data.find(it =>it.id === id) return ( <div> <ul> <li>{id}</li> <li>{title}</li> <li>{findRes.content}</li> </ul> </div> ) } }
总结:
-
路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{id:1,title:'你好啊'}}}>详情
-
注册路由(无需声明,正常注册):
-
接收参数:this.props.location.state
接收参数:this.props.location.state
-
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './detail'
export default class Message extends Component {
state = {
messageArr:[
{
id:'01',
title:'消息1'
},
{
id:'02',
title:'消息2'
},
{
id:'03',
title:'消息3'
},
{
id:'04',
title:'消息4'
}
]
}
replaceShow = (id,title) =>{
//replace跳转+携带params参数
// this.props.history.replace(`/home/message/detail/${id}/${title}`)
//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`)
//replace跳转+携带state参数
this.props.history.replace({pathname:'/home/message/detail',state:{id,title}})
}
pushShow = (id,title) =>{
//push+携带params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)
//push+携带search参数
// this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`)
//push+携带state参数
this.props.history.push({pathname:'/home/message/detail',state:{id,title}})
}
render() {
const {messageArr} = this.state
return (
<div>
<ul className="list-group">
{
messageArr.map(msg =>{
return (
<div key={msg.id} style={{display:'flex',marginTop:'5px'}}>
{/* 向路由组件传递params参数 */}
{/* <Link to={`/home/message/detail/${msg.id}/${msg.title}`} className="list-group-item">{msg.title}</Link> */}
{/* 像路由传递search参数 */}
{/* <Link key={msg.id} to={`/home/message/detail/?id=${msg.id}&title=${msg.title}`} className="list-group-item">{msg.title}</Link> */}
{/* 向路由传递state参数 */}
<Link replace={true} key={msg.id} to={{pathname:'/home/message/detail',state:msg}} className="list-group-item">{msg.title}</Link>
<div className="btn-group" role="group" aria-label="Basic mixed styles example">
<button onClick={() =>this.pushShow(msg.id,msg.title)} type="button" className="btn btn-danger" style={{marginLeft:'5px'}}>push查看</button>
<button onClick={() =>this.replaceShow(msg.id,msg.title)} type="button" className="btn btn-warning" style={{marginLeft:'5px'}} >replace查看</button>
</div>
</div>
)
})
}
</ul>
<hr/>
{/* {声明接受params参数} */}
{/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}
{/* {声明接收search参数(search参数无须声明接收)} */}
{/* <Route path="/home/message/detail" component={Detail}/> */}
{/* {state参数无需接收} */}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
编程式路由导航
借助this.props.history对象身上的Api对操作路由跳转,前进,后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
class Header extends Component {
back = () =>{
this.props.history.goBack()
}
forward = () =>{
this.props.history.goForward()
}
go = () =>{
this.props.history.go(-2)
}
render() {
console.log(this.props)
return (
<div>
<div className="btn-group" role="group" aria-label="Basic mixed styles example">
<button onClick={this.back} type="button" className="btn btn-danger">回退</button>
<button onClick={this.forward} type="button" className="btn btn-warning">前进</button>
<button onClick={this.go} type="button" className="btn btn-success">go</button>
</div>
</div>
)
}
}
//withRouter可以加工一般组件,让一般组件具备路由组件特有的Api
//withRouter的返回值是一个新的组件
export default withRouter(Header)
-
底层原理不一样
BrowserRouter的使用是H5的history Api,不兼容IE9及一下的版本
HashRouter使用的是URL的哈希值
-
url表现形式不一样
BrowserRouter的路径中没用#列如:localhst:3000/demo/test
HashRouter中路径包含#列如:localhost:3000/#/demo/test
-
刷新后对路由state参数的影响
BrowserRouter没用任何影响,因为state保存在history对象中
HashRouter刷新后会导致state参数的丢失
-
备注:HashRouter可以解决一些路径错误相关的问题
-
redux是什么?
redux是react中进行state状态管理的JS库,一般是管理多个组件中共享数据的,它并不是react的插件,是一个独立的库vue和angular等等一些框架都是可以使用的
React-Redux
是Redux
的官方React
绑定库。它能够使你的React
组件从Redux store
中读取数据,并且向store
分发actions
以更新数据 -
什么情况下需要使用redux?
- 某个组件的状态,需要让其他组件可以随时拿到(共享)
- 一个组件需要改变另一个组件的状态(通信)
- 总体原则:能不用就不用,如果不用比较吃力才考虑使用
- action
- 动作的对象
- 包含两个属性
- type:标识属性,值为字符串,唯一,必要属性
- data:数据属性,值类型任意,可选属性
- 列子:{type:'ADD_STUDENT',data:{name:'tom',age:18}}
- reducer
- 用于初始化状态,加工状态
- 加工时,根据旧的state和action,产生新的state的纯函数
- store
- 将state,action,reducer联系在一起的对象
- 如何得到此对象?
- import {createStore} from 'redux'
- import reducer from './reducers'
- subscribe(listner):注册监听,当产生了新的state时自动调用
-
安装redux
yarn add redux react-redux
-
创建redux文件夹,并创建reducer.js
/* 1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数 2.reducer函数会接到两个参数分别为:之前的状态(preState),动作对象(action) */ const initState = 0 export default function countReducer(preState = initState,action){ console.log(preState,action) const {type,data} = action switch(type){ case 'INCREMENT'://加 return preState + data case 'DECREMENT'://减 return preState - data default://初始化 return preState } }
-
在redux文件夹下创建store.js
//该文件专门用于暴露一个store对象,整个应用只有一个store对象 //引入createStire,专门用与创建核心的store对象 import { legacy_createStore as createStore } from 'redux' //引入为Count组件服务的reducer import countReducer from './count_reducer' export default createStore(countReducer)
-
在组件中使用
import React, { Component } from 'react' //引入store用于获取redux中保存的状态 import store from '../../redux/store' export default class Count extends Component { //加法 increment = () =>{ store.dispatch({type:'INCREMENT',data:1}) } render() { return ( <div> <h2>当前求和为:{store.getState()}</h2> <div style={{display:'flex'}}> <div className="dropdown"> <button className="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false"> 请选择添加数值 </button> <ul className="dropdown-menu" aria-labelledby="dropdownMenuButton1"> <li className="dropdown-item">1</li> <li className="dropdown-item">3</li> <li className="dropdown-item">5</li> </ul> </div> <div className="btn-group" role="group" aria-label="Basic mixed styles example"> <button type="button" className="btn btn-danger">当前求和为奇数再加</button> <button type="button" className="btn btn-warning">异步加</button> <button onClick={this.increment} type="button" className="btn btn-success">+</button> <button type="button" className="btn btn btn-info">-</button> </div> </div> </div> ) } }
-
在index.js中检测store中状态的改变,一旦发生改变需重新渲染App
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from 'react-router-dom'; import App from './App'; import store from "./redux/store"; store.subscribe(() =>{ //检测redux中状态的变化,只要变化,旧调用render ReactDOM.render(<App/>,document.getElementById('root')) }) ReactDOM.render( <BrowserRouter> <App/> </BrowserRouter> ,document.getElementById('root'))
-
在redux文件夹创建constant.js文件
/** * 该模块是用于定义action对象中type类型的常量值,便于管理的同时防止程序员单词写错 */ export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
-
在redux文件夹中创建count_action.js文件
/** * 该文件专门为count组件生成action对象 */ import { DECREMENT, INCREMENT } from './constant' export const createIncrementAction = data =>({type:INCREMENT,data}) export const createDecrementAction = data =>({type:DECREMENT,data})
-
在redux文件夹中修改count_reducer.js文件
/* 1.该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数 2.reducer函数会接到两个参数分别为:之前的状态(preState),动作对象(action) */ import { DECREMENT, INCREMENT } from './constant' const initState = 0 export default function countReducer(preState = initState,action){ console.log(preState,action) const {type,data} = action switch(type){ case INCREMENT://加 return preState + data case DECREMENT://减 return preState - data default://初始化 return preState } }
-
在组件中使用redux
import React, { Component } from 'react' //引入store用于获取redux中保存的状态 import { createDecrementAction, createIncrementAction } from '../../redux/count_action' import store from '../../redux/store' export default class Count extends Component { //加法 increment = () =>{ store.dispatch(createIncrementAction(1)) } //减法 decrement = () =>{ store.dispatch(createDecrementAction(1)) } render() { return ( <div> <h2>当前求和为:{store.getState()}</h2> <div style={{display:'flex'}}> <div className="btn-group" role="group" aria-label="Basic mixed styles example"> <button type="button" className="btn btn-danger">当前求和为奇数再加</button> <button type="button" className="btn btn-warning">异步加</button> <button onClick={this.increment} type="button" className="btn btn-success">+</button> <button onClick={this.decrement} type="button" className="btn btn btn-info">-</button> </div> </div> </div> ) } }
-
执行命令下载cnpm i redux-thunk 来处理异步任务
-
在count_action中创建异步action
/** * 该文件专门为count组件生成action对象 */ import { DECREMENT, INCREMENT } from './constant' //同步Aaction返回一个对象{type:xxx,data:xxx} export const createIncrementAction = data =>({type:INCREMENT,data}) export const createDecrementAction = data =>({type:DECREMENT,data}) //异步Action须返回一个函数,异步action中一般都会调用同步action export const createIncrementAsyncAction = (data,time) =>{ return (dispatch) =>{ setTimeout(() =>{ dispatch(createIncrementAction(data))//通知reducer更新状态 },time) } }
-
在store中使用中间件redux-thunk
//该文件专门用于暴露一个store对象,整个应用只有一个store对象 //引入createStire,专门用与创建核心的store对象 import { applyMiddleware, legacy_createStore as createStore } from 'redux' //引入为Count组件服务的reducer import countReducer from './count_reducer' //引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' export default createStore(countReducer,applyMiddleware(thunk))
-
在组件中使用异步分发异步action
import React, { Component } from 'react' //引入store用于获取redux中保存的状态 import { createDecrementAction, createIncrementAction, createIncrementAsyncAction } from '../../redux/count_action' import store from '../../redux/store' export default class Count extends Component { //加法 increment = () =>{ store.dispatch(createIncrementAction(1)) } //减法 decrement = () =>{ store.dispatch(createDecrementAction(1)) } //异步加 incrementAsync = () =>{ store.dispatch(createIncrementAsyncAction(1,500)) } render() { return ( <div> <h2>当前求和为:{store.getState()}</h2> <div style={{display:'flex'}}> <div className="btn-group" role="group" aria-label="Basic mixed styles example"> <button type="button" className="btn btn-danger">当前求和为奇数再加</button> <button onClick={this.incrementAsync} type="button" className="btn btn-warning">异步加</button> <button onClick={this.increment} type="button" className="btn btn-success">+</button> <button onClick={this.decrement} type="button" className="btn btn btn-info">-</button> </div> </div> </div> ) } }
总结:异步action不是必须要写的,完全可以自己等待异步任务的结果再去分发同步action
react-redux是react官方推出的redux绑定库。react-redux将所有组件分为两大类:UI组件和容器组件,其中所有容器组件包裹着UI组件,构成父子关系。容器组件负责和redux交互,里面使用redux API函数,UI组件负责页面渲染,不使用任何redux API。容器组件会给UI组件传递redux中保存对的状态(state)和操作状态的方法(action)。
react-redux模型图
-
创建containers文件夹并创建Count容器的jsx文件
-
在Count文件夹下的Index.jsx中连接UI和容器
//引入Count的UI组件 import CountUI from '../../component/count/index' //引入connect用于连接UI组件与redux import { connect } from 'react-redux' export default connect()(CountUI)
-
在组件中使用容器下中的Count组件,并将store传入(不传入store将会报错)
import React, { Component } from 'react' import Count from './containers/Count/index' import store from './redux/store' export default class App extends Component { render() { return ( <div> <Count store={store}/> </div> ) } }
-
创建Count容器并连接UI和容器
//引入Count的UI组件 import CountUI from '../../component/count/index' //引入connect用于连接UI组件与redux import { connect } from 'react-redux' //引入action import { createDecrementAction, createIncrementAction, createIncrementAsyncAction } from '../../redux/count_action' /* 1.mapStateToProps函数返回的对象中的key,就作为传递UI组件props的key, 2.valu就作为UI组件props的value 3.mapStateToProps用于传递状态 */ function mapStateToProps(state){ return {count:state} } /* 1.mapDispatchToProps函数返回对象中的key就作为传递给UI组件props的key 2.value就作为UI组件props的value 3.mapDispatchToProps用于操作状态 */ function mapDispatchToProps(dispatch){ return { //通知redux执行加法 'increment':num => dispatch(createIncrementAction(num)), 'decrement':num => dispatch(createDecrementAction(num)), 'incrementAsync':(num,time) =>dispatch(createIncrementAsyncAction(num,time)) } } //使用connect()()创建并暴露一个Count的容器组件 export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
-
在组件中使用该创建容器时mapStateToProps和mapDispatchToProps传递的状态和操作状态的方法
import React, { Component } from 'react' export default class Count extends Component { //加法 increment = () =>{ this.props.increment(1) } //减法 decrement = () =>{ this.props.decrement(1) } //异步加 incrementAsync = () =>{ this.props.incrementAsync(2,500) } render() { const {count} = this.props return ( <div> <h2>当前求和为:{count}</h2> <div style={{display:'flex'}}> <div className="btn-group" role="group" aria-label="Basic mixed styles example"> <button onClick={this.incrementAsync} type="button" className="btn btn-warning">异步加</button> <button onClick={this.increment} type="button" className="btn btn-success">+</button> <button onClick={this.decrement} type="button" className="btn btn btn-info">-</button> </div> </div> </div> ) } }
-
此时可以去除index.js监听store的方法(connect内部已经实现监听)
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from 'react-router-dom'; import App from './App'; ReactDOM.render( <BrowserRouter> <App/> </BrowserRouter>, document.getElementById('root'))
-
在使用Count容器的地方传递store给props
import React, { Component } from 'react' import Count from './containers/Count/index' import store from './redux/store' export default class App extends Component { render() { return ( <div> {/* {给容器组件传递store} */} <Count store={store}/> </div> ) } }
-
一般简写
//引入Count的UI组件 import CountUI from '../../component/count/index' //引入connect用于连接UI组件与redux import { connect } from 'react-redux' //引入action import { createDecrementAction, createIncrementAction, createIncrementAsyncAction } from '../../redux/count_action' //使用connect()()创建并暴露一个Count的容器组件 export default connect( state =>({count:state}), dispatch =>( { 'increment':num => dispatch(createIncrementAction(num)), 'decrement':num => dispatch(createDecrementAction(num)), 'incrementAsync':(num,time) =>dispatch(createIncrementAsyncAction(num,time)) }) )(CountUI)
-
精简写法
//引入Count的UI组件 import CountUI from '../../component/count/index' //引入connect用于连接UI组件与redux import { connect } from 'react-redux' //引入action import { createDecrementAction, createIncrementAction, createIncrementAsyncAction } from '../../redux/count_action' //使用connect()()创建并暴露一个Count的容器组件 export default connect( state =>({count:state}), { increment:createIncrementAction, decrement:createDecrementAction, incrementAsync:createIncrementAsyncAction } )(CountUI)
-
最终写法
-
在action中简写action的名字
/** * 该文件专门为count组件生成action对象 */ import { DECREMENT, INCREMENT } from '../constant' //同步Aaction返回一个对象{type:xxx,data:xxx} export const increment = data =>({type:INCREMENT,data}) export const decrement = data =>({type:DECREMENT,data}) //异步Action须返回一个函数,异步action中一般都会调用同步action export const incrementAsync = (data,time) =>{ return (dispatch) =>{ setTimeout(() =>{ dispatch(increment(data))//通知reducer更新状态 },time) } }
-
在容器中使用
//引入Count的UI组件 import CountUI from './count_ui' //引入connect用于连接UI组件与redux import { connect } from 'react-redux' //引入action import { decrement, increment, incrementAsync } from '../../redux/actions/count' //使用connect()()创建并暴露一个Count的容器组件 export default connect( ({count,person}) =>({count,personLength:person.length}), { increment, decrement, incrementAsync } )(CountUI)
-
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import App from './App';
import store from './redux/store';
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root'))
总结:使用Provider之后,则在别的容器中无需传入store即可使用(避免多组件使用store重复传入使用provicer以后只需传入一次)
-
创建Person组件的action,reducer
-
创建action
import { ADD_PERSON } from '../constant' //创建增加一个人的Action动作对象 export const createPersonAction = data => ({type:ADD_PERSON,data})
-
创建reducer
import { ADD_PERSON } from '../constant' const initState = [] export default function createReducer(preState = initState,action){ const {type,data} = action switch(type){ case ADD_PERSON: return [data,...preState] default: return preState } }
-
-
创建Person组件和容器
-
创建Person组件
import React, { Component } from 'react' import { v4 as uuidv4 } from 'uuid' export default class Person extends Component { state = { FormData:{ name:'', age:'' } } handleNameChange = (event) =>{ const {value:name} = event.target const {FormData} = this.state this.setState({ FormData:{...FormData,name} }) } handleAgeChange = (event) =>{ const {value:age} = event.target const {FormData} = this.state this.setState({ FormData:{...FormData,age} }) } handleSubmit = (event) =>{ event.preventDefault() const id = uuidv4() const person = {id,...this.state.FormData} this.props.addPerson(person) } render() { const {person,count} = this.props return ( <div> <h2>当前求和为:{count}</h2> <div className="card" style={{width:'300px'}}> <div className="card-body"> <form> <div className="mb-3"> <label className="form-label">Name</label> <input onChange={this.handleNameChange} placeholder='Please enter your name' type="text" id='NameInput' className="form-control" aria-describedby="emailHelp"/> </div> <div className="mb-3"> <label className="form-label">Age</label> <input onChange={this.handleAgeChange} placeholder='Please enter your age' type="text" className="form-control" id="AgeInput"/> </div> <button onClick={this.handleSubmit} type="submit" className="btn btn-primary">Submit</button> </form> </div> </div> <hr/> <table className="table table-striped" style={{width:'700px'}}> <thead> <tr> <th scope="col">id</th> <th scope="col">Name</th> <th scope="col">Age</th> </tr> </thead> <tbody> { person.map(item =>{ return ( <tr key={item.id}> <th scope="row">{item.id}</th> <td>{item.name}</td> <td>{item.age}</td> </tr> ) }) } </tbody> </table> </div> ) } }
-
创建Person容器
import { connect } from 'react-redux' import { createPersonAction } from '../../redux/actions/person' import PersonUI from './person_ui' export default connect( ({person,count}) =>({person,count}), { addPerson:createPersonAction } )(PersonUI)
3.文件目录为
总结:此时即可在person组件中使用count组件的求和值(count组件代码见上一节)
-
- 一类特别的函数,只要是同样的输入(实参),必定得到同意的输出(返回)
- 必须遵守以下一些约束
- 不得改写参数的数据
- 不会产生任何副作用,列如网络请求,输入和输出设备
- 不能调用Date.now()或者Math.random()等不纯的方法
- redux的reducer函数必须是一个纯函数
-
在谷歌应用商店下载插件Redux DevTools
-
在项目中通过cnpm i redux-devtools-extension下载插件
-
在redux中store.js文件中使用 redux-devtools-extension
//该文件专门用于暴露一个store对象,整个应用只有一个store对象 //引入createStire,专门用与创建核心的store对象 import { applyMiddleware, combineReducers, legacy_createStore as createStore } from 'redux' //引入为Count组件服务的reducer import countReducer from './reducers/count' //引入Person组件服务的reducer import personReducer from './reducers/person' //引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' //引入redux-devtools-extension import { composeWithDevTools } from 'redux-devtools-extension' //汇总所有的reducer变为一个总的reducer const allReducer = combineReducers({ count:countReducer, person:personReducer }) //暴露store export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
- 执行npm run build打包文件
- 全局下载cnpm i serve -g
- 执行serve build即可开启一个服务器
-
setState的使用
import React, { Component } from 'react' export default class StateDemo extends Component { state = { count:0 } increment = () =>{ const {count} = this.state //对象式的setState的第一种写法 // this.setState({ // count:count+1 // }) //对象式的setState第二种写法callback是可选的回调函数他在状态更新完毕和页面render后调用 this.setState({count:count+1},() =>{ console.log(this.state.count)//此时可以拿到最新的state }) } Hanincrement = () =>{ /** * 函数式setState * setState(updater,[callback]) ----函数式setState * 1.updater 可以接收到state,props * 2.callback是可选的回调函数,他在状态更新,界面也更新后的render调用后才调用 */ this.setState((state,props) =>({count:state.count+props.x})) } render() { return ( <div> <h1>当前求和为:{this.state.count}</h1> <button onClick={this.increment} type="button" className="btn btn btn-info">对象式点我+1</button> <button onClick={this.Hanincrement} type="button" className="btn btn btn-info">函数式点我+1</button> </div> ) } }
总结
- 对象式的setState是函数式setState的简写方式(语法糖)
- 使用原则
- 如果新状态不依赖于原状态 =>使用对象方式
- 如果新状态依赖于原状态 =>函数方式
- 如果需要在setState()执行后获取最新数据,要在第二个callback函数中读取
-
如何使用lazy对路由进行懒加载
import React, { Component, lazy, Suspense } from 'react' import { Route, Switch } from 'react-router-dom' import { Redirect } from 'react-router-dom/cjs/react-router-dom.min' import AppStyle from './App.module.css' import Loading from './component/loading' import MyNavLink from './component/mylink' //懒加载 const Home = lazy(() =>import('./component/home')) const About = lazy(() =>import('./component/abount')) export default class App extends Component { render() { return ( <div> <div style={{display:'flex',padding:'100px'}}> <div className={AppStyle.left}> <MyNavLink to='/home/aaa'>Home</MyNavLink> <MyNavLink style={{marginTop:'10px'}} to='/about'>About</MyNavLink> </div> <div className={AppStyle.right}> <Suspense fallback={<Loading/>}> <Switch> <Route path='/home' component={Home}/> <Route path='/about' component={About}/> <Redirect to='/home'/> </Switch> </Suspense> </div> </div> </div> ) } }
总结:懒加载的组件必须采用const xxx = lazy(() =>import('xxxxx'))的形式引入
需要懒加载的组件必须通过Suspense组件进行包裹,并指定fallback为一个标签或者组件(作用是在路由懒加载时为了防止网速过慢造成的白屏需要指定一个组件进行空白填充一般为loading组件)
-
为什么会有Hooks?
介绍Hooks之前,首先要给大家说一下React的组件创建方式,一种是类组件*,一种是纯函数组件*,并且React团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。也就是说组件的最佳写法应该是函数,而不是类
但是我们知道,在以往开发中类组件和纯函数组件的区别是很大的,纯函数组件有着类组件不具备的多种特点,简单列举几条
- 纯函数组件没有状态
- 纯函数组件没有生命周期
- 纯函数组件没有
this
- 只能是纯函数
这就注定,我们所推崇的函数组件,只能做UI展示的功能,涉及到状态的管理与切换,我们不得不用类组件或者redux,但我们知道类组件的也是有缺点的,比如,遇到简单的页面,你的代码会显得很重,并且每创建一个类组件,都要去继承一个React实例,至于Redux,更不用多说,很久之前Redux的作者就说过,“能用React解决的问题就不用Redux”,等等一系列的话。关于React类组件redux的作者又有话说
- 大型组件很难拆分和重构,也很难测试。
- 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
- 组件类引入了复杂的编程模式,比如 render props 和高阶组件
-
什么是Hooks?
'Hooks'的单词意思为“钩子”。 **React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。**而React Hooks 就是我们所说的“钩子”。 那么Hooks要怎么用呢?“你需要写什么功能,就用什么钩子”。对于常见的功能,React为我们提供了一些常用的钩子,当然有特殊需要,我们也可以写自己的钩子。下面是React为我们提供的默认的四种最常用钩子
- useState()
- userContext()
- userReducer()
- useEffect()
不同的钩子为函数引入不同的外部功能,我们发现上面四种钩子都带有
use
前缀,React约定,钩子一律使用use
前缀命名。所以,你自己定义的钩子都要命名为useXXX。
-
我们知道,纯函数组件没有状态,
useState()
用于为函数组件引入状态import { useState } from 'react' export default function Count(){ const [count,setCount] = useState(0) const [name,setName] = useState('wmq') function add(){ // setCount(count+1)//第一种写法 setCount(count =>count+1) } function changeName(){ setName('jack') } return ( <div> <h2>当前求和为:{count}</h2> <h2>我的名字是:{name}</h2> <button onClick={add}>点我加1</button> <button onClick={changeName}>点我改名</button> </div> ) }
-
Effect Hook可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
-
React中的副作用操作
- 发ajax请求数据获取
- 设置订阅/启动定时器
- 手动更改真实dom
-
语法和说明:
useEffect(() =>{ //此时相当于componenDidUpdate()(每次render都会执行) }) useEffect(() =>{ //此时相当于componentDidMount()(只会在页面加载完执行一次) },[]) useEffect(() =>{ return () =>{ //在返回函数里面的内容可以看作是componentWillUnmount() } })
总结:useEffect Hook可以看作如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
-
使用举例
import { useEffect, useState } from 'react' import ReactDom from 'react-dom' export default function Count(){ const [count,setCount] = useState(0) const [name,setName] = useState('wmq') function add(){ // setCount(count+1)//第一种写法 setCount(count =>count+1) } function changeName(){ setName('jack') } function onMount(){ ReactDom.unmountComponentAtNode(document.getElementById('root')) } /** * useEffect如果不传第二个参数则 */ useEffect(() =>{ let timer = setInterval(() =>{ setCount(count =>count+1) },500) return () =>{ clearInterval(timer) } },[]) return ( <div> <h2>当前求和为:{count}</h2> <h2>我的名字是:{name}</h2> <button onClick={add}>点我加1</button> <button onClick={changeName}>点我改名</button> <button onClick={onMount}>卸载组件</button> </div> ) }
-
refHook可以在函数组件中查找存储/查找组件标签或任意数据
import { useRef } from 'react' export default function Input(){ const myRef = useRef() function show(){ console.log(myRef.current.value) } return( <div style={{width:'300px'}}> <div className="input-group mb-3"> <span className="input-group-text" id="inputGroup-sizing-default">Default</span> <input ref={myRef} type="text" className="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default"/> <button onClick={show}>点我提示数据</button> </div> </div> ) }
-
什么是Fragment?
在向 DOM 树批量添加元素时,一个好的实践是创建一个document.createDocumentFragment,先将元素批量添加到 DocumentFragment 上,再把 DocumentFragment 添加到 DOM 树,减少了 DOM操作次数的同时也不会创建一个新元素
和 DocumentFragment 类似,React 也存在 Fragment 的概念,用途很类似。在 React 16之前,Fragment 的创建是通过扩展包 react-addons-create-fragment 创建,而 React 16 中则通过<React.Fragment></React.Fragment> 直接创建 ‘Fragment'。
-
如何使用?
-
import { Fragment, useRef } from 'react' export default function Input(){ const myRef = useRef() function show(){ console.log(myRef.current.value) } return( <Fragment> <div className="input-group mb-3" style={{width:'300px'}}> <span className="input-group-text" id="inputGroup-sizing-default">Default</span> <input ref={myRef} type="text" className="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default"/> <button onClick={show}>点我提示数据</button> </div> </Fragment> ) }
Fragment组件可以支持传递一个key
-
-
空标签的使用也可以代替Fragment但是空标签无法传递key值
import React, { Component } from 'react' import Demo from './component/fragment' export default class App extends Component { render() { return ( <> <Demo/> </> ) } }
-
什么是Context?
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
-
什么时候使用Context?
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据。如果父组件想和自己的子组件孙组件层级进行数据传输时,我们不需要像props那样父传子,子传孙这样。使用Context(上下文)可以直接将父组件的指定数据传给所有子孙组件
-
如何使用Context?
import React, { Component, createContext } from 'react' //创建Contenxt对象 const MyContext = createContext() const {Provider} = MyContext export default class A extends Component { state = { username:'tom', age:18 } render() { const {username,age} = this.state return ( <div> <h3>我是A组件</h3> <h4>我的用户名是{username}</h4> <Provider value={{username,age}}> <B/> </Provider> </div> ) } } class B extends Component { //声明接收context static contextType = MyContext render() { const {username,age} = this.context return ( <div> <h3>我是B组件</h3> <h4>我从A组件接收的用户名是:{username}年龄是{age}</h4> <C/> </div> ) } } class C extends Component { //声明接收context static contextType = MyContext render() { const {username,age} = this.context return ( <div> <h3>我是C组件</h3> <h4>我从A组件接收的用户名是:{username}年龄是{age}</h4> </div> ) } }
-
以上方法只适用于类式组件,下面介绍使用useContext()在函数组件中获取上下文
import React, { createContext, useContext, useState } from 'react' //创建Contenxt对象 const MyContext = createContext() const {Provider} = MyContext export default function A(){ const [user,setUser] = useState({username:'tom',age:18}) return ( <div> <h3>我是A组件</h3> <h4>我的用户名是{user.username}</h4> <Provider value={{...user}}> <B/> </Provider> </div> ) } function B() { const {username,age} = useContext(MyContext) return ( <div> <h3>我是B组件</h3> <h4>我从A组件接收的用户名是:{username}年龄为{age}</h4> <C/> </div> ) } function C(){ const {username,age} = useContext(MyContext) return ( <div> <h3>我是C组件</h3> <h4>我从A组件接收的用户名是:{username}年龄为{age}</h4> </div> ) }
-
什么是PurComponent?
React.PureComponent 与 React.Component 几乎完全相同,但 React.PureComponent 通过props和state的浅对比来实现 shouldComponentUpate()
在普通的Component中可以使用shouldComponentUpdate来优化性能。
-
如何使用PurComponent?
import React, { PureComponent } from 'react' export default class Car extends PureComponent { state = { carName:'法拉利' } changeCar = () =>{ this.setState({carName:'劳斯莱斯'}) } render() { const {carName} = this.state console.log('Car--render') return ( <> <span>我的车为:{carName}</span> <br/> <button onClick={this.changeCar}>点击换车</button> <hr /> <Child/> </> ) } } class Child extends PureComponent{ render(){ //使用PureComponent以后父组件render若子组件不发生改变则不会调用子组件render console.log('child--render') return ( <> <span>我的车为劳斯莱斯</span> </> ) } }
注意: PureComponent只是对数据进行浅对比,如果存在obj或者复杂数据结构则不可用。 在PureComponent中,如果包含比较复杂的数据结构,可能会因深层的数据不一致而产生错误的否定判断,从而shouldComponentUpdate结果返回false,导致界面得不到更新
-
如何向组件内部动态传入带有内容的结构(标签)?
Vue中: 使用slot技术,也就是通过组件标签传入结构<A><B/></A> React中: 使用children props:通过组件标签体传入结构 使用render props:通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
-
children props
<A> <B>XXX</B> </A> 问题如果B组件需要A组件的数据 =>这种办法无法做到
-
render props
<A render= {data =><B data={data}/>}/> A组件:{this.props.render(需要传递给此位置组件的数据)} B组件:读取A组件传入的数据显示{this.props.data}
-
实际案例封装一个组件可以用button切换指定组件的显示
import React, { Component } from 'react' export default class Show extends Component { state = { isShow:true } changeShow = () =>{ this.setState(({isShow})=>({isShow:!isShow})) } render() { const {isShow} = this.state return ( <> <div className="card" style={{width:'300px'}}> <div className="card-body"> {this.props.render(isShow)} </div> <button onClick={this.changeShow} type="button" className="btn btn-primary">是否显示</button> </div> </> ) } }
import React, { Component } from 'react' export default class List extends Component { render() { const {isShow} = this.props return ( <> { isShow ? <ul className="list-group"> <li className="list-group-item">An item</li> <li className="list-group-item">A second item</li> <li className="list-group-item">A third item</li> <li className="list-group-item">A fourth item</li> <li className="list-group-item">And a fifth one</li> </ul> : '' } </> ) } }
import React, { Component } from 'react' import List from './component/List' import Show from './component/show' export default class App extends Component { render() { return ( <> <Show render={isShow =><List isShow={isShow}/>}/> </> ) } }
-
什么是错误边界?
默认情况下,若一个组件在渲染期间(render) 发生错误,会导致整个组件树全部被卸载
错误边界是一种
React
组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误 -
错误边界的注意点?
错误边界无法捕获一下场景中出现的错误:
- 自身的错误
- 异步的错误
- 事件中的错误
- 事件中的错误
-
总结:仅处理渲染子组件期间的同步错误
-
错误边界的使用?
import React, { Component } from 'react' export default class Child extends Component { state = { // users:[ // {id:'001',name:'tom',age:18}, // {id:'002',name:'jack',age:19}, // {id:'003',name:'sery',age:20} // ] users:"abc" } render() { const {users} = this.state return ( <> <h2>我是child组件</h2> <ul className="list-group" style={{width:'300px'}}> { users.map(user =>{ return <li key={user.id} className="list-group-item">{user.name+'-----'+user.age}</li> }) } </ul> </> ) } }
此时child组件会报错 users.map is not a function
在Parent组件中捕获错误
import React, { Component } from 'react' import Child from './child' export default class Parent extends Component { state = { hasError:''//用于标识子组件是否产生错误 } static getDerivedStateFromError(error){ //如果parent子组件出现任何错误都会调用这个钩子并会捕获误传进来 return {hasError:error} } //如果渲染当中如果子组件出现错误会执行这个生命周期钩子 componentDidCatch(){ //一般统计错误次数发送给后台 console.log('统计错误信息,反馈给服务器,通知编码人员解决bug') } render() { return ( <> <h2>我是Parent组件</h2> {this.state.hasError ? <h2>当前网络不稳定,请稍后再试</h2> : <Child/>} </> ) } }