第1章:React入门
React简介
官网
1. 英文官网: https://reactjs.org/
2. 中文官网: https://react.docschina.org/
介绍描述
1. 用于动态构建用户界面的 JavaScript 库(只关注于视图)
2. 由Facebook开源
React的特点
1. 声明式编码
2. 组件化编码
3. React Native 可以使用于移动端开发
4. 高效(优秀的Diffing算法)
React高效的原因
1. 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
2. DOM Diffing算法, 最小化页面重绘。
React的基本使用
效果

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>hello_react</title> </head> <body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel" > const VDOM = <h1>Hello,React</h1> ReactDOM.render(VDOM,document.getElementById('test')) </script> </body> </html>
|
相关js库
- react.js:React核心库。
2. react-dom.js:提供操作DOM的react扩展库。
3. babel.min.js:解析JSX语法代码转为JS代码的库。
创建虚拟DOM的两种方式

1. 纯JS方式(一般不用,比如写标签嵌套比较麻烦)
比如下面创建一个h1标签里面再创建一个span标签
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>2_使用js创建虚拟DOM</title> </head> <body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" > const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React')) ReactDOM.render(VDOM,document.getElementById('test')) </script> </body> </html>
|
- JSX方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>1_使用jsx创建虚拟DOM</title> </head> <body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel" > const VDOM = ( <h1 id="title"> /* 这里就可以很容易实现标签嵌套了 */ <span>Hello,React</span> </h1> ) ReactDOM.render(VDOM,document.getElementById('test')) </script> </body> </html>
|
虚拟DOM与真实DOM
- React提供了一些API来创建一种 “特别” 的一般js对象
** const VDOM = React.createElement('xx',{id:'xx'},'xx')**
上面创建的就是一个简单的虚拟DOM对象
2. 虚拟DOM对象(上面我们在jsx里面写的标签就是虚拟DOM)最终都会被React转换为真实的DOM
3. 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新。
关于虚拟DOM:
1.本质是Object类型的对象(一般对象)
2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
React JSX
效果

JSX
- 全称: JavaScript XML
2. react定义的一种类似于XML的JS扩展语法: JS + XML本质是**React.createElement(component, props, ...children)**方法的语法糖
3. 作用: 用来简化创建虚拟DOM
1) 写法:**var ele = <h1>Hello JSX!</h1>**
2) 注意1:它不是字符串, 也不是HTML/XML标签
3) 注意2:它最终产生的就是一个JS对象
4. 标签名任意: HTML标签或其它标签
5. 标签属性任意: HTML标签属性或其它属性
6. 基本语法规则
1) 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
2) 遇到以 { 开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含
7. babel.js的作用
1) 浏览器不能直接解析JSX代码, 需要babel转译JSX为纯JS的代码才能运行
2) 只要用了JSX,都要加上type=”text/babel”, 声明需要babel来处理
jsx语法规则:
1.定义虚拟DOM时,不要写引号。
2.标签中混入JS表达式时要用{}。
3.样式的类名指定不要用class,要用className。
4.内联样式,要用style={{key:value}}的形式去写。<br />
|
5.只有一个根标签
6.标签必须闭合
7.标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。(小写字母开头说明就是普通的html标签)
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。(大写字母开头就说明是自己自定义的组件)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>jsx语法规则</title> <style> .title{ background-color: orange; width: 200px; } </style> </head> <body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel" > const myId = 'aTgUiGu' const myData = 'HeLlo,rEaCt' //1.创建虚拟DOM const VDOM = ( <div> <h2 className="title" id={myId.toLowerCase()}>
<span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span> </h2> <h2 className="title" id={myId.toUpperCase()}> <span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span> </h2> <input type="text"/> </div> ) //2.渲染虚拟DOM到页面 ReactDOM.render(VDOM,document.getElementById('test')) </script> </body> </html>
|
渲染虚拟DOM(元素)
- 语法:
**ReactDOM.render(virtualDOM, containerDOM)**
2. 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示
3. 参数说明
1) 参数一: 纯js或jsx(一般都是jsx)创建的虚拟dom对象
2) 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是html页面中的一个div)
一定注意区分:【js语句(代码)】与【js表达式】
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test () {}
2.语句(代码):
下面这些都是语句(代码):
(1).if(){}
(2).for(){}
(3).switch(){case:xxxx}
JSX练习
需求: 动态展示如下列表
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>jsx小练习</title> </head> <body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel" > const data = ['Angular','React','Vue'] const VDOM = ( <div> <h1>前端js框架列表</h1> <ul> { data.map((item,index)=>{ return <li key={index}>{item}</li> }) } </ul> </div> ) ReactDOM.render(VDOM,document.getElementById('test')) </script> </body> </html>
|
模块与组件、模块化与组件化的理解
模块
- 理解:向外提供特定功能的js程序, 一般就是一个js文件
2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
3. 作用:复用js, 简化js的编写, 提高js运行效率
组件
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
2. 为什么要用组件: 一个界面的功能更复杂
3. 作用:复用编码, 简化项目编码, 提高运行效率
模块化
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
组件化
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
第2章:React面向组件编程
2.1. 基本理解和使用
2.1.1. 使用React开发者工具调试

2.1.2. 效果

创建组件的两种方法
函数式组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>1_函数式组件</title> </head> <body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> function MyComponent(){ console.log(this); return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2> } ReactDOM.render(<MyComponent/>,document.getElementById('test')) </script> </body> </html>
|
执行了ReactDOM.render(…….之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
类式组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>2_类式组件</title> </head> <body>
<div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class MyComponent extends React.Component { render(){ console.log('render中的this:',this); return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2> } } ReactDOM.render(<MyComponent/>,document.getElementById('test')) </script> </body> </html>
|
执行了ReactDOM.render(…….之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例(React帮我们new),并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
2.1.3. 注意
- 组件名必须首字母大写
2. 虚拟DOM元素只能有一个根元素
3. 虚拟DOM元素必须有结束标签
2.1.4. 渲染类组件标签的基本流程
- React内部会创建组件实例对象
2. 调用render()得到虚拟DOM, 并解析为真实DOM
3. 插入到指定的页面元素内部
2.2. 组件实例(也就是类组件,不是函数组件)的三大核心属性1: state
2.2.1. 效果
需求: 定义一个展示天气信息的组件
1. 默认展示天气炎热或凉爽
2. 点击文字切换天气

**2.2.2. 理解 **
- state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
2. 组件被称为”状态机”, 通过更新组件的state来更新对应的页面显示(重新渲染组件)
2.2.3. 强烈注意
- 组件中render方法中的this为组件实例对象
2. 组件自定义的方法中this为undefined,如何解决?
a) 强制绑定this: 通过函数对象的bind()
b) 箭头函数
3. 状态数据,不能直接修改或更新(需要调用相关的方法,setState)
State基础写法
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>state</title> </head>
<body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Weather extends React.Component {
constructor(props) { console.log('constructor'); super(props) this.state = { isHot: false, wind: '微风' } this.changeWeather = this.changeWeather.bind(this) } render() { console.log('render'); console.log(this) const { isHot, wind } = this.state return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1> } changeWeather() { console.log('changeWeather'); const isHot = this.state.isHot this.setState({ isHot: !isHot }) console.log(this); } } ReactDOM.render(<Weather />, document.getElementById('test'))
</script> </body>
</html>
|
state的简写方式
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>state简写方式</title> </head>
<body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Weather extends React.Component { state = { isHot: false, wind: '微风' } render() { const { isHot, wind } = this.state return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1> } changeWeather = () => { const isHot = this.state.isHot this.setState({ isHot: !isHot }) } } ReactDOM.render(<Weather />, document.getElementById('test'))
</script> </body>
</html>
|
2.3. 组件三大核心属性2: props
2.3.1. 效果
需求: 自定义用来显示一个人员信息的组件
1. 姓名必须指定,且为字符串类型;
2. 性别为字符串类型,如果性别没有指定,默认为男
3. 年龄为字符串类型,且为数字类型,默认值为18

2.3.2. 理解
- 每个组件对象都会有props(properties的简写)属性
2. 组件标签的所有属性都保存在props中
2.3.3. 作用
- 通过标签属性从组件外向组件内(父子传递)传递变化的数据
2. 注意: 组件内部(子组件不能修改props数据,也就是说props是只读的单向数据流)不要修改props数据
2.3.4. 编码操作
- 内部读取某个属性值
**this**.**props**.**name**
2. 对props中的属性值进行类型限制和必要性限制
第一种方式(React v15.5 开始已弃用):
_Person_.**propTypes **= {<br /> **name**: **React**.**PropTypes**.**string**.isRequired,<br /> **age**: **React**.**PropTypes**.**number**<br />}
第二种方式(新):使用prop-types库进限制(需要引入prop-types库,因为大部分情况下我们都不需要进行类型限制,所以React就没帮我们引入)
_Person_.**propTypes **= {<br /> **name**: **PropTypes**.**string**.isRequired,<br /> **age**: **PropTypes**.**number**. <br />}
3. 扩展属性: 将对象的所有属性通过props传递(这也是js的扩展运算符语法。对于对象的扩展运算符,会把属性名作为等号左边,属性值作为等号右边,不过我觉得没啥用,因为可以直接传一个对象过去。)<**Person **{...**_person_**}/>
4. 默认属性值:
Person.**defaultProps **= {<br /> **age**: 18,<br /> **sex**:**'男'<br />**}
5. 组件类的构造函数
**constructor**(props){<br /> **super**(props)<br /> **console**.log(props)_//打印所有属性<br />_}
props的基本使用
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>props基本使用</title> </head>
<body> <div id="test1"></div> <div id="test2"></div> <div id="test3"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Person extends React.Component { render() { const { name, age, sex } = this.props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age + 1}</li> </ul> ) } } ReactDOM.render(<Person name="jerry" age={19} sex="男" />, document.getElementById('test1')) ReactDOM.render(<Person name="tom" age={18} sex="女" />, document.getElementById('test2'))
const p = { name: '老刘', age: 18, sex: '女' } ReactDOM.render(<Person {...p} />, document.getElementById('test3')) </script> </body>
</html>
|
或者直接传递一个对象到子组件,就不用用展开运算符了
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>props基本使用</title> </head>
<body> <div id="test1"></div>
<script type="text/javascript" src="./js/react.development.js"></script> <script type="text/javascript" src="./js/react-dom.development.js"></script> <script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel"> class Person extends React.Component { render() { console.log(this.props); console.log(this.props.p.name) const { name, age, sex } = this.props.p return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age + 1}</li> </ul> ) } } const p = { name: '老刘', age: 18, sex: '女' } ReactDOM.render(<Person p={p} />, document.getElementById('test1')) </script> </body>
</html>
|
对props进行限制(类型,是否必传)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>对props进行限制</title> </head> <body> <div id="test1"></div> <div id="test2"></div> <div id="test3"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script> <script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel"> class Person extends React.Component{ render(){ const {name,age,sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age+1}</li> </ul> ) } } Person.propTypes = { name:PropTypes.string.isRequired, sex:PropTypes.string, age:PropTypes.number, speak:PropTypes.func, } Person.defaultProps = { sex:'男', age:18 } ReactDOM.render(<Person name={100} speak={speak}/>,document.getElementById('test1')) ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'} ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
function speak(){ console.log('我说话了'); } </script> </body> </html>
|
props的简写方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>对props进行限制</title> </head> <body> <div id="test1"></div> <div id="test2"></div> <div id="test3"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script> <script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel"> class Person extends React.Component{
constructor(props){ super(props) console.log('constructor',this.props); }
static propTypes = { name:PropTypes.string.isRequired, sex:PropTypes.string, age:PropTypes.number, }
static defaultProps = { sex:'男', age:18 } render(){ const {name,age,sex} = this.props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age+1}</li> </ul> ) } }
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1')) </script> </body> </html>
|
函数组件使用props
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>对props进行限制</title> </head> <body> <div id="test1"></div> <div id="test2"></div> <div id="test3"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script> <script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel"> 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, sex:PropTypes.string, age:PropTypes.number, }
Person.defaultProps = { sex:'男', age:18 } ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1')) </script> </body> </html>
|
2.4. 组件三大核心属性3: refs与事件处理
2.4.1. 效果
需求: 自定义组件, 功能说明如下:
1. 点击按钮, 提示第一个输入框中的值
2. 当第2个输入框失去焦点时, 提示这个输入框中的值
效果如下:

2.4.2. 理解
组件内的标签可以定义ref属性来标识自己
2.4.3. 编码
- 字符串形式的ref
**<input ref="input1"/>**
2. 回调形式的ref**<input ref={(c)=>{this.input1 = c}}/>**
3. createRef创建ref容器
myRef = React.createRef()
2.4.4 示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>1_字符串形式的ref</title> </head> <body> <div id="test"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Demo 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="点击按钮提示数据"/> //给虚拟dom绑定点击事件是onClick,然后值是一个函数,注意只需要函数名就可以了 <button onClick={this.showData}>点我提示左侧的数据</button> <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test')) </script> </body> </html>
|
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>1_字符串形式的ref</title> </head>
<body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Demo extends React.Component { showData = () => { const { input1 } = this alert(input1.value) } showData2 = () => { const { input2 } = this alert(input2.value) } render() { return ( <div> // 下面这一行中的ref中的回调函数会被react默认调用,也就是默认赋值,之后就可以通过实例对象的input1属性进行操作document对象了 //这个参数传的c就是这个标签的虚拟dom对象,然后再给这个实例对象身上的input1属性进行赋值 <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据" /> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据" /> </div> ) } } ReactDOM.render(<Demo a="1" b="2" />, document.getElementById('test')) </script> </body>
</html>
|
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>3_回调ref中回调执行次数的问题</title> </head>
<body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Demo extends React.Component {
state = { isHot: false }
showInfo = () => { const { input1 } = this alert(input1.value) }
changeWeather = () => { const { isHot } = this.state this.setState({ isHot: !isHot }) }
saveInput = (c) => { this.input1 = c; console.log('@', c); }
render() { const { isHot } = this.state return ( <div> <h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2> {/*以内联函数的方式进行,这样的话如果发生修改操作的话会调用两次函数,第一次参数为null,第二次才为实际传的参数 <input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
{/*下面这个是定义成class的绑定函数,就是在class里面写一个函数进行调用*/} <input ref={this.saveInput} type="text" /><br /><br /> <button onClick={this.showInfo}>点我提示输入的数据</button> <button onClick={this.changeWeather}>点我切换天气</button> </div> ) } } ReactDOM.render(<Demo />, document.getElementById('test')) </script> </body>
</html>
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>4_createRef</title> </head> <body> <div id="test"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Demo extends React.Component{ myRef = React.createRef() myRef2 = React.createRef() showData = ()=>{ alert(this.myRef.current.value); } showData2 = ()=>{ alert(this.myRef2.current.value); } render(){ return( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/> </div> ) } } ReactDOM.render(<Demo/>,document.getElementById('test')) </script> </body> </html>
|
2.4.5. 事件处理
- 通过onXxx属性指定事件处理函数(比如点击事件、失去焦点事件等等)(注意大小写)
1) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件—————— 为了更好的兼容性
2) React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)————————为了的高效
2. 通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref,可以直接使用event就好辣
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>事件处理</title> </head>
<body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Demo extends React.Component { myRef = React.createRef() myRef2 = React.createRef()
showData = (event) => { console.log(event.target); alert(this.myRef.current.value); }
showData2 = (event) => { alert(event.target.value); }
render() { return ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" /> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" /> </div> ) } } ReactDOM.render(<Demo />, document.getElementById('test')) </script> </body>
</html>
|
2.5. 收集表单数据
2.5.1. 效果
需求: 定义一个包含表单的组件
输入用户名密码后, 点击登录提示输入信息

2.5.2. 理解
包含表单的组件分类
1. 受控组件(vue里面的双向数据绑定)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>2_受控组件</title> </head> <body> <div id="test"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Login extends React.Component{
state = { username:'', password:'' }
saveUsername = (event)=>{ this.setState({username:event.target.value}) }
savePassword = (event)=>{ this.setState({password:event.target.value}) }
handleSubmit = (event)=>{ event.preventDefault() const {username,password} = this.state alert(`你输入的用户名是:${username},你输入的密码是:${password}`) }
render(){ return( <form onSubmit={this.handleSubmit}> 用户名:<input onChange={this.saveUsername} type="text" name="username"/> 密码:<input onChange={this.savePassword} 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"> <title>1_非受控组件</title> </head> <body> <div id="test"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Login extends React.Component{ handleSubmit = (event)=>{ event.preventDefault() const {username,password} = this alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`) } render(){ return( <form 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>
|
高阶函数和函数的柯里化
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>高阶函数_函数柯里化</title> </head>
<body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Login extends React.Component { state = { username: '', password: '' }
saveFormData = (dataType) => { return (event) => {
this.setState({ [dataType]: event.target.value }) } }
handleSubmit = (event) => { event.preventDefault() const { username, password } = this.state alert(`你输入的用户名是:${username},你输入的密码是:${password}`) } render() { return ( <form onSubmit={this.handleSubmit}> 用户名:<input onChange={this.saveFormData('username')} type="text" name="username" /> 密码:<input onChange={this.saveFormData('password')} 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"> <title>2_不用函数柯里化的实现</title> </head> <body> <div id="test"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Login extends React.Component{ state = { username:'', password:'' }
saveFormData = (dataType,event)=>{ this.setState({[dataType]:event.target.value}) }
handleSubmit = (event)=>{ event.preventDefault() const {username,password} = this.state alert(`你输入的用户名是:${username},你输入的密码是:${password}`) } render(){ return( <form onSubmit={this.handleSubmit}> 用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/> 密码:<input onChange={event => this.saveFormData('password',event) } type="password" name="password"/> <button>登录</button> </form> ) } } ReactDOM.render(<Login/>,document.getElementById('test')) </script> </body> </html>
|
2.6. 组件的生命周期
2.6.1. 效果
需求:定义组件实现以下功能:
1. 让指定的文本做显示 / 隐藏的渐变动画
2. 从完全可见,到彻底消失,耗时2S
3. 点击“不活了”按钮从界面中卸载组件

<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <title>1_引出生命周期</title> </head>
<body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Life extends React.Component {
state = { opacity: 1 }
death = () => { ReactDOM.unmountComponentAtNode(document.getElementById('test')) }
componentDidMount() { console.log('componentDidMount'); this.timer = setInterval(() => { let { opacity } = this.state opacity -= 0.1 if (opacity <= 0) opacity = 1 this.setState({ opacity }) }, 200); }
componentWillUnmount() { clearInterval(this.timer) }
render() { console.log('render'); return ( <div> <h2 style={{ opacity: this.state.opacity }}>React学不会怎么办?</h2> <button onClick={this.death}>不活了</button> </div> ) } } ReactDOM.render(<Life />, document.getElementById('test')) </script> </body>
</html>
|
2.6.2. 理解
- 组件从创建到死亡它会经历一些特定的阶段。
2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
2.6.3. 生命周期流程图(旧)

生命周期的三个阶段(旧)
** 1. 初始化阶段:** 由ReactDOM.render()触发—初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount()
** 2. 更新阶段: **由组件内部this.setSate()或父组件重新render触发
1. shouldComponentUpdate() 组件是否应该更新的阀门 返回true为放行,为false就不往下执行了(默认返回为真)
2. componentWillUpdate() 组件将要更新(强制更新,这里可以不更改state就强制更新)
3. render()
4. componentDidUpdate() 更新完毕的钩子
**3. 卸载组件: **由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>2_react生命周期(旧)</title> </head> <body> <div id="test"></div> <script type="text/javascript" src="../js/react.development.js"></script> <script type="text/javascript" src="../js/react-dom.development.js"></script> <script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> class Count extends React.Component{
constructor(props){ console.log('Count---constructor'); super(props) this.state = {count:0} }
add = ()=>{ const {count} = this.state this.setState({count:count+1}) }
death = ()=>{ ReactDOM.unmountComponentAtNode(document.getElementById('test')) }
force = ()=>{ this.forceUpdate() }
componentWillMount(){ console.log('Count---componentWillMount'); }
componentDidMount(){ console.log('Count---componentDidMount'); }
componentWillUnmount(){ console.log('Count---componentWillUnmount'); }
shouldComponentUpdate(){ console.log('Count---shouldComponentUpdate'); return true }
componentWillUpdate(){ console.log('Count---componentWillUpdate'); }
componentDidUpdate(){ console.log('Count---componentDidUpdate'); }
render(){ console.log('Count---render'); const {count} = this.state return( <div> <h2>当前求和为:{count}</h2> <button onClick={this.add}>点我+1</button> <button onClick={this.death}>卸载组件</button> <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button> </div> ) } } class A extends React.Component{ state = {carName:'奔驰'}
changeCar = ()=>{ this.setState({carName:'奥拓'}) }
render(){ return( <div> <div>我是A组件</div> <button onClick={this.changeCar}>换车</button> <B carName={this.state.carName}/> </div> ) } } class B extends React.Component{ componentWillReceiveProps(props){ console.log('B---componentWillReceiveProps',props); }
shouldComponentUpdate(){ console.log('B---shouldComponentUpdate'); return true } componentWillUpdate(){ console.log('B---componentWillUpdate'); }
componentDidUpdate(){ console.log('B---componentDidUpdate'); }
render(){ console.log('B---render'); return( <div>我是B组件,接收到的车是:{this.props.carName}</div> ) } } ReactDOM.render(<Count/>,document.getElementById('test')) </script> </body> </html>
|
2.6.4. 生命周期流程图(新)
旧版生命周期流程图

新版生命周期流程图

生命周期的三个阶段(新)
1. 初始化阶段: 由ReactDOM.render()触发—初次渲染
1. constructor()
2. getDerivedStateFromProps 当state的值在任何时候都取决于props的时候才用这个钩子
3. render()
4. componentDidMount()
** 2. 更新阶段: **由组件内部this.setSate()或父组件重新render触发
1. getDerivedStateFromProps
2. shouldComponentUpdate()
3. render()
4. getSnapshotBeforeUpdate 快照值可以是任何类型的值
5. componentDidUpdate() 这里函数里面可以有两个参数,一个preProps,一个preState
**3. 卸载组件: **由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount()
2.6.4.1 具体实例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>3_react生命周期(新)</title> </head> <body> <div id="test"></div> <script type="text/javascript" src="../js/17.0.1/react.development.js"></script> <script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script> <script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel"> class Count extends React.Component{ constructor(props){ console.log('Count---constructor'); super(props) this.state = {count:0} }
add = ()=>{ const {count} = this.state this.setState({count:count+1}) }
death = ()=>{ ReactDOM.unmountComponentAtNode(document.getElementById('test')) }
force = ()=>{ this.forceUpdate() } static getDerivedStateFromProps(props,state){ console.log('getDerivedStateFromProps',props,state); return null }
getSnapshotBeforeUpdate(){ console.log('getSnapshotBeforeUpdate'); return 'atguigu' }
componentDidMount(){ console.log('Count---componentDidMount'); }
componentWillUnmount(){ console.log('Count---componentWillUnmount'); }
shouldComponentUpdate(){ console.log('Count---shouldComponentUpdate'); return true }
componentDidUpdate(preProps,preState,snapshotValue){ console.log('Count---componentDidUpdate',preProps,preState,snapshotValue); } render(){ console.log('Count---render'); const {count} = this.state return( <div> <h2>当前求和为:{count}</h2> <button onClick={this.add}>点我+1</button> <button onClick={this.death}>卸载组件</button> <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button> </div> ) } } ReactDOM.render(<Count count={199}/>,document.getElementById('test')) </script> </body> </html>
|
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>4_getSnapShotBeforeUpdate的使用场景</title> <style> .list{ width: 200px; height: 150px; background-color: skyblue; overflow: auto; } .news{ height: 30px; } </style> </head> <body> <div id="test"></div> <script type="text/javascript" src="../js/17.0.1/react.development.js"></script> <script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script> <script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel"> class NewsList extends React.Component{
state = {newsArr:[]}
componentDidMount(){ setInterval(() => { const {newsArr} = this.state const news = '新闻'+ (newsArr.length+1) this.setState({newsArr:[news,...newsArr]}) }, 1000); }
getSnapshotBeforeUpdate(){ return this.refs.list.scrollHeight }
componentDidUpdate(preProps,preState,height){ this.refs.list.scrollTop += this.refs.list.scrollHeight - height }
render(){ return( <div className="list" ref="list"> { this.state.newsArr.map((n,index)=>{ return <div key={index} className="news">{n}</div> }) } </div> ) } } ReactDOM.render(<NewsList/>,document.getElementById('test')) </script> </body> </html>
|
2.6.5. 重要的勾子
- render:初始化渲染或更新渲染调用
2. componentDidMount:开启监听, 发送ajax请求
3. componentWillUnmount:做一些收尾工作, 如: 清理定时器
2.6.6. 即将废弃的勾子
- componentWillMount
2. componentWillReceiveProps
3. componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。虽然这三个基本都不用
2.7. 虚拟DOM与DOM Diffing算法
2.7.1. 效果
需求:验证虚拟DOM Diffing算法的存在

我们可以看见,上面的图片是span标签内部一直在更新,diff算法的最小粒度是标签,所以最好把内容都写在标签里面,也就是说时间改变了,前面的It is也是跟着改变的,但是前面的输入框和Hello不会进行改变,因为根本就没变,如果输入框也改变的话输入框的内容会被清空
2.7.2. 基本原理图
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?
1. 虚拟DOM中key的作用:
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用
2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
2. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。

验证
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>key的作用</title> </head> <body> <div id="test"></div>
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 慢动作回放----使用index索引值作为key
初始数据: {id:1,name:'小张',age:18}, {id:2,name:'小李',age:19}, 初始的虚拟DOM: <li key=0>小张---18<input type="text"/></li> <li key=1>小李---19<input type="text"/></li>
更新后的数据: {id:3,name:'小王',age:20}, {id:1,name:'小张',age:18}, {id:2,name:'小李',age:19}, 更新数据后的虚拟DOM: <li key=0>小王---20<input type="text"/></li> <li key=1>小张---18<input type="text"/></li> <li key=2>小李---19<input type="text"/></li>
-----------------------------------------------------------------
慢动作回放----使用id唯一标识作为key
初始数据: {id:1,name:'小张',age:18}, {id:2,name:'小李',age:19}, 初始的虚拟DOM: <li key=1>小张---18<input type="text"/></li> <li key=2>小李---19<input type="text"/></li>
更新后的数据: {id:3,name:'小王',age:20}, {id:1,name:'小张',age:18}, {id:2,name:'小李',age:19}, 更新数据后的虚拟DOM: <li key=3>小王---20<input type="text"/></li> <li key=1>小张---18<input type="text"/></li> <li key=2>小李---19<input type="text"/></li>
*/ class Person extends React.Component{
state = { persons:[ {id:1,name:'小张',age:18}, {id:2,name:'小李',age:19}, ] }
add = ()=>{ const {persons} = this.state const p = {id:persons.length+1,name:'小王',age:20} this.setState({persons:[p,...persons]}) }
render(){ return ( <div> <h2>展示人员信息</h2> <button onClick={this.add}>添加一个小王</button> <h3>使用index(索引值)作为key</h3> <ul> { this.state.persons.map((personObj,index)=>{ return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li> }) } </ul> <hr/> <hr/> <h3>使用id(数据的唯一标识)作为key</h3> <ul> { this.state.persons.map((personObj)=>{ return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li> }) } </ul> </div> ) } }
ReactDOM.render(<Person/>,document.getElementById('test')) </script> </body> </html>
|
第3章:React应用(基于React脚手架)
3.1. 使用create-react-app创建react应用
3.1.1. react脚手架
- xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
1. 包含了所有需要的配置(语法检查、jsx编译、devServer…)
2. 下载好了所有相关的依赖
3. 可以直接运行一个简单效果
2. react提供了一个用于创建react项目的脚手架库: create-react-app
3. 项目的整体技术架构为: react + webpack + es6 + eslint
4. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
3.1.2. 创建项目并启动
第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹:cd hello-react
第四步,启动项目:npm start
3.1.3. react脚手架项目结构
public ---- 静态资源文件夹<br /> favicon.icon ------ 网站页签图标<br /> **index.html -------- 主页面**<br /> logo192.png ------- logo图<br /> logo512.png ------- logo图<br /> manifest.json ----- 应用加壳(套壳ios和Android)的配置文件<br /> robots.txt -------- 爬虫协议文件<br />src ---- 源码文件夹<br /> App.css -------- App组件的样式<br /> **App.js --------- App组件**<br /> App.test.js ---- 用于给App做测试<br /> index.css ------ 样式<br /> **index.js ------- 入口文件(区别于vue,vue的是main.js)**<br /> logo.svg ------- logo图<br /> reportWebVitals.js<br /> --- 页面性能分析文件(需要web-vitals库的支持)<br /> setupTests.js<br /> ---- 组件单元测试的文件(需要jest-dom库的支持)<br />需要注意的是,组件的名称和Vue的一样,首字母大写,并且每个组件需要遵守相应书写的格式<br />编写组件的快捷键是类组件rcc。函数组件rfc
3.1.4. 功能界面的组件化编码流程(通用)
- 拆分组件: 拆分界面,抽取组件
2. 实现静态组件: 使用组件实现静态页面效果
3. 实现动态组件
3.1 动态显示初始化数据
3.1.1 数据类型
3.1.2 数据名称
3.1.2 保存在哪个组件?
3.2 交互(从绑定事件监听开始)
**3.2. 组件的组合使用-TodoList **
功能: 组件化实现此功能
1. 显示所有todo列表
2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本

1.拆分组件、实现静态组件,注意:className、style的写法
2.动态初始化列表,如何确定将数据放在哪个组件的state中?
——某个组件使用:放在其自身的state中
——某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
3.关于父子之间通信:
1.【父组件】给【子组件】传递数据:通过props传递
2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
4.注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value
5.状态在哪里,操作状态的方法就在哪里
第4章:React ajax
4.1. 理解
4.1.1. 前置说明
- React本身只关注于界面, 并不包含发送ajax请求的代码
2. 前端应用需要通过ajax请求与后台进行交互(json数据)
3. react应用中需要集成第三方ajax库(或自己封装)
4.1.2. 常用的ajax请求库
- jQuery: 比较重, 如果需要另外引入不建议使用
2. axios: 轻量级, 建议使用
1) 封装XmlHttpRequest对象的ajax
2) promise风格
3) 可以用在浏览器端和node服务器端
react脚手架配置代理总结
方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
|
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
- 此外,所有的请求本来需要5000端口的数据的,现在需要给3000(自己开前端的服务端口)端口发。也相当于给自己的代理服务器发请求,让代理服务器去发请求

方法二
- 第一步:创建代理配置文件 (这个配置文件会由React加载,所以名称不能变)
在src下创建配置文件:src/setupProxy.js
|
- 编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app) { app.use( proxy('/api1', { target: 'http://localhost:5000', changeOrigin: true,
pathRewrite: {'^/api1': ''} }), proxy('/api2', { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2': ''} }) ) }
|
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
4.2. axios
4.2.1. 文档
https://github.com/axios/axios
4.2.2. 相关API
GET请求
axios.get('/user?ID=12345') .then(function (response) { console.log(response.data); }) .catch(function (error) { console.log(error); });
axios.get('/user', { params: { ID: 12345 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
|
POST请求
axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
|
4.3. 案例—github用户搜索
4.3.1. 效果

请求地址: https://api.github.com/search/users?q=xxxxxx
4.4. 消息订阅-发布机制
这个东西适用于任何组件的沟通!!!
1. 工具库: PubSubJS
2. 下载: npm install pubsub-js –save
yarn add pubsub-js –save
3. 使用:
1) import PubSub from ‘pubsub-js’ //引入,这个引入是在组件中引入。
2) PubSub.subscribe(‘delete’, function(data){ }); //订阅
3) PubSub.publish(‘delete’, data) //发布消息
具体代码
import React, { Component } from 'react' import Search from './components/Search' import List from './components/List'
export default class App extends Component { render() { return ( <div className="container"> <Search/> <List/> </div> ) } }
|
import React, { Component } from 'react' import PubSub from 'pubsub-js' import axios from 'axios'
export default class Search extends Component {
search = ()=>{ const {keyWordElement:{value:keyWord}} = this PubSub.publish('atguigu',{isFirst:false,isLoading:true}) axios.get(`/api1/search/users?q=${keyWord}`).then( response => { PubSub.publish('atguigu',{isLoading:false,users:response.data.items}) }, error => { PubSub.publish('atguigu',{isLoading:false,err:error.message}) } ) }
render() { return ( <section className="jumbotron"> <h3 className="jumbotron-heading">搜索github用户</h3> <div> <input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> <button onClick={this.search}>搜索</button> </div> </section> ) } }
|
import React, { Component } from 'react' import PubSub from 'pubsub-js' import './index.css'
export default class List extends Component {
state = { users:[], isFirst:true, isLoading:false, err:'', }
componentDidMount(){ this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{ this.setState(stateObj) }) }
componentWillUnmount(){ PubSub.unsubscribe(this.token) }
render() { const {users,isFirst,isLoading,err} = this.state return ( <div className="row"> { isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> : isLoading ? <h2>Loading......</h2> : err ? <h2 style={{color:'red'}}>{err}</h2> : users.map((userObj)=>{ return ( <div key={userObj.id} className="card"> <a rel="noreferrer" href={userObj.html_url} target="_blank"> <img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/> </a> <p className="card-text">{userObj.login}</p> </div> ) }) } </div> ) } }
|
4.5. 扩展:Fetch

4.5.1. 文档
- https://github.github.io/fetch/
2. https://segmentfault.com/a/1190000003810652
4.5.2. 特点
- fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求
2. 老版本浏览器可能不支持
4.5.3. 相关API
- GET请求
fetch(url).then(function(response) { return response.json() }).then(function(data) { console.log(data) }).catch(function(e) { console.log(e) });
|
- POST请求
fetch(url, { method: "POST", body: JSON.stringify(data), }).then(function(data) { console.log(data) }).catch(function(e) { console.log(e) })
|
github搜索案例相关知识点
1.设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
2.ES6小知识点:解构赋值+重命名
let obj = {a:{b:1}}
const {a} = obj; //传统解构赋值
const {a:{b}} = obj; //连续解构赋值
const {a:{b:value}} = obj; //连续解构赋值+重命名
3.消息订阅与发布机制
1.先订阅,再发布(理解:有一种隔空对话的感觉)
2.适用于任意组件间通信
3.要在组件的componentWillUnmount中取消订阅
4.fetch发送请求(关注分离的设计思想)
try { const response= await fetch(`/api1/search/users2?q=${keyWord}`) const data = await response.json() console.log(data); } catch (error) { console.log('请求出错',error); }
|
第5章:React路由
5.1. 相关理解
5.1.1. SPA的理解
- 单页Web应用(single page web application,SPA)。
2. 整个应用只有一个完整的页面。
3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
4. 数据都需要通过ajax请求获取, 并在前端异步展现。
5.1.2. 路由的理解
1. 什么是路由?
1. 一个路由就是一个映射关系(key:value)
2. key为路径, value可能是function或component
2. 路由分类
1. 后端路由:
1) 理解: value是function, 用来处理客户端提交的请求。
2) 注册路由:router.get(path, function(req, res))
3) 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
2. 前端路由:
1) 浏览器端路由,value是component,用于展示页面内容。
2) 注册路由:
3) 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
history的结构是栈的结构,如果push会压栈,回去是弹栈,如果是replace是会替换栈顶的那一条数据
5.1.3. react-router-dom的理解
- react的一个插件库。
2. 专门用来实现一个SPA应用。
3. 基于react的项目基本都会用到此库。
5.2. react-router-dom相关API
这个是5版本的,后面有6版本的
5.2.1. 内置组件
2.
3.
4.
5.
6.
7.
5.2.2. 其它
- history对象
2. match对象
3. withRouter函数
5.3. 基本路由使用
5.3.1. 效果

5.3.2. 准备
- 下载react-router-dom: npm install –save react-router-dom@5
2. 引入bootstrap.css:
总结
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
<Link to="/xxxxx">Demo</Link>
3.展示区写Route标签进行路径的匹配
<Route path='/xxxx' component={Demo}/>
4.的最外侧包裹了一个或
import React, { Component } from 'react' import {Link,Route} from 'react-router-dom' import Home from './components/Home' import About from './components/About'
export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group">
{/* 原生html中,靠<a>跳转不同的页面 */} {/* <a className="list-group-item" href="./about.html">About</a> <a className="list-group-item active" href="./home.html">Home</a> */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */} <Link className="list-group-item" to="/about">About</Link> <Link className="list-group-item" to="/home">Home</Link> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> </div> </div> </div> </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') )
|
路由组件与一般组件
1.写法不同:
一般组件:
路由组件:
2.存放位置不同:
一般组件:components文件夹下
路由组件:pages文件夹下
3.接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:程序员没有机会进行传递,由react固定给我们传递,所以会接收到三个固定的属性
history: go: ƒ go(n) goBack: ƒ goBack() goForward: ƒ goForward() push: ƒ push(path, state) replace: ƒ replace(path, state) location: pathname: "/about" search: "" state: undefined match: params: {} path: "/about" url: "/about"
|
封装NavLink
import React, { Component } from 'react' import {Route} from 'react-router-dom' import Home from './pages/Home' import About from './pages/About' import Header from './components/Header' import MyNavLink from './components/MyNavLink'
export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <Header/> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group">
{/* 原生html中,靠<a>跳转不同的页面 */} {/* <a className="list-group-item" href="./about.html">About</a> <a className="list-group-item active" href="./home.html">Home</a> */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */} {/* 标签体内的About会传入子组件的props属性中,属性名称叫children */} <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> </div> </div> </div> </div> </div> ) } }
|
import React, { Component } from 'react' import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component { render() { return ( <NavLink activeClassName="atguigu" className="list-group-item" {...this.props}/> ) } }
|
NavLink 与封装 NavLink
1.NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
Switch的使用
通常状况下,path和component是一一对应的关系,但是如果我们不配置switch的话,会出现以下问题
<div className="panel-body"> {} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Route path="/home" component={Test}/> </div>
|
但是如果配置了Switch的话
<div className="panel-body"> {} <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Route path="/home" component={Test}/> </Switch> </div>
|
解决多级路径刷新页面样式丢失的问题
1.public/index.html 中 引入样式时不写 ./ 写 / (常用)
2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
3.使用HashRouter,因为锚点后面的就不算路径了
<div className="panel-body"> {} <Route path="/haohao/about" component={About}/> <Route path="/haohao/home" component={Home}/> </div>
|
shift+刷新=强制刷新
路由的严格匹配和模糊匹配
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:<Route exact={true} path="/about" component={About}/>
3.严格匹配不要随便开启,需要的时候再开,有些时候开启会导致无法继续匹配二级路由
举例
模糊匹配:给多了可以,给少了不行,默认开启这个
<div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home/a/b">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Switch> {/* 这里其实是模糊匹配,下面的home是可以匹配上的 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> </Switch> </div> </div> </div> </div>
|
精准匹配:主打的就是一个多了不行少了也不行,但是尽量不要用这个
<div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home/a/b">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Switch> {/* 这里开启了严格匹配,下面的home是匹配不到的 */} <Route exact path="/about" component={About}/> <Route exact path="/home" component={Home}/> </Switch> </div> </div> </div> </div>
|
Redirect的使用
1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/about"/> </Switch>
|
3.完整代码:
import React, { Component } from 'react'
import {Route,Switch,Redirect} from 'react-router-dom' import Home from './pages/Home' import About from './pages/About' import Header from './components/Header' import MyNavLink from './components/MyNavLink'
export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <Header /> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Switch> <Route path="/about" component={About} /> <Route path="/home" component={Home} /> {/* Redirect相当于一个兜底的人,如果访问的路径都匹配不上,就访问about */} <Redirect to="/about" /> </Switch> </div> </div> </div> </div> </div> ); } }
|
5.4. 嵌套路由使用
注意注意!嵌套路由不能使用严格匹配,开启会无法导致二级路由
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的
效果

代码
一级路由是/home
二级路由是/home/news和/home/message
import React, { Component } from "react"; import MyNavLink from "../../components/MyNavLink"; import { Route, Switch, Redirect } from "react-router-dom"; import News from "./News"; import Message from "./Message";
export default class Home extends Component { render() { return ( <div> <h3>我是Home的内容</h3> <div> <ul className="nav nav-tabs"> {/* 需要注意的是,这里的一级路由的路径不能省略,需要跟在二级路由的前面 */} <li> <MyNavLink to="/home/news">News</MyNavLink> </li> {/* 需要注意的是,这里的一级路由的路径不能省略,需要跟在二级路由的前面 */} <li> <MyNavLink to="/home/message">Message</MyNavLink> </li> </ul> {/* 注册路由 */} <Switch> {/* 需要注意的是,这里的一级路由的路径不能省略,需要跟在二级路由的前面 */} <Route path="/home/news" component={News} /> <Route path="/home/message" component={Message} /> <Redirect to="/home/news" /> </Switch> </div> </div> ); } }
|
5.5. 向路由组件传递参数数据
向路由组件传递params参数
1.params参数
路由链接(携带参数):<Link to=’/demo/test/tom/18’}>详情
注册路由(声明接收):
接收参数:this.props.match.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" }, ], }; render() { const { messageArr } = this.state; return ( <div> <ul> {messageArr.map((msgObj) => { return ( <li key={msgObj.id}> {/* 向路由组件传递params参数 */} <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}> {msgObj.title} </Link> </li> ); })} </ul> <hr /> {/* 声明接收params参数 */} {/* 下面这个参数可以在子组件中以props的形式接收,props直接给封装好了 */} <Route path="/home/message/detail/:id/:title" component={Detail} /> </div> ); } }
|
import React, { Component } from "react";
const DetailData = [ { id: "01", content: "你好,中国" }, { id: "02", content: "你好,尚硅谷" }, { id: "03", content: "你好,未来的自己" }, ]; export default class Detail extends Component { render() { console.log(this.props); const { id, title } = this.props.match.params; const findResult = DetailData.find((detailObj) => { return detailObj.id === id; }); return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ); } }
|
向路由组件传递search参数

路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.search
备注:获取到的 search 是 urlencoded 编码字符串(key=value&key=value),需要借助 querystring 解析
具体代码:
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" }, ], }; render() { const { messageArr } = this.state; return ( <div> <ul> {messageArr.map((msgObj) => { return ( <li key={msgObj.id}> {/* 向路由组件传递search参数 */} {/* 这里的search参数会落在子组件的props属性的location下的search属性下,所以叫search属性不叫query属性 */} <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`} > {msgObj.title} </Link> </li> ); })} </ul> <hr /> {/* search参数无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail} /> </div> ); } }
|
import React, { Component } from "react"; import qs from "querystring";
const DetailData = [ { id: "01", content: "你好,中国" }, { id: "02", content: "你好,尚硅谷" }, { id: "03", content: "你好,未来的自己" }, ]; export default class Detail extends Component { render() { console.log(this.props); const { search } = this.props.location; const { id, title } = qs.parse(search.slice(1)); const findResult = DetailData.find((detailObj) => { return detailObj.id === id; }); return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ); } }
|
向路由组件传递state参数
这个state和组件的state不一样
**这个state是存储在history的,这说明我们如果清理历史记录的话,再刷新页面就看不到这个state了 **
之前那两个就算是清除历史记录数据也不会丢,因为又通过url进行传过去了

- 路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
- 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test}/>
- 接收参数:
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" }, ], }; render() { const { messageArr } = this.state; return ( <div> <ul> {messageArr.map((msgObj) => { return ( <li key={msgObj.id}> {/* 向路由组件传递state参数 */} {/* 这里通过对象进行传输数据,state可以在子组件的location中的state中获取 pathname之前没写是因为他是默认属性,spring里面也有这种情况*/} <Link to={{ pathname: "/home/message/detail", state: { id: msgObj.id, title: msgObj.title }, }} > {msgObj.title} </Link> </li> ); })} </ul> <hr /> {/* state参数无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail} /> </div> ); } }
|
import React, { Component } from 'react'
const DetailData = [ {id:'01',content:'你好,中国'}, {id:'02',content:'你好,尚硅谷'}, {id:'03',content:'你好,未来的自己'} ] export default class Detail extends Component { render() { console.log(this.props); const {id,title} = this.props.location.state || {}
const findResult = DetailData.find((detailObj)=>{ return detailObj.id === id }) || {} return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ) } }
|
5.6. 多种路由跳转方式
push和replace
开启replace模式,替换push模式
<MyNavLink replace to="/about">About</MyNavLink>
编程式导航
借助 this.prosp.history 对象上的 API 对操作路由跳转、前进、后退(但是这也意味着非路由组件就用不了这个API了)
- -this.prosp.history.push()
- -this.prosp.history.replace()
- -this.prosp.history.goBack()
- -this.prosp.history.goForward()
- -this.prosp.history.go()

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'}, ] }
replaceShow = (id,title)=>{
this.props.history.replace(`/home/message/detail`,{id,title}) }
pushShow = (id,title)=>{
this.props.history.push(`/home/message/detail`,{id,title}) }
back = ()=>{ this.props.history.goBack() }
forward = ()=>{ this.props.history.goForward() }
go = ()=>{ this.props.history.go(-2) }
render() { const {messageArr} = this.state return ( <div> <ul> { messageArr.map((msgObj)=>{ return ( <li key={msgObj.id}>
{/* 向路由组件传递params参数 */} {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递search参数 */} {/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}
{/* 向路由组件传递state参数 */} <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button> <button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button> </li> ) }) } </ul> <hr/> {/* 声明接收params参数 */} {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}
{/* search参数无需声明接收,正常注册路由即可 */} {/* <Route path="/home/message/detail" component={Detail}/> */}
{/* state参数无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail}/>
<button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> <button onClick={this.go}>go</button>
</div> ) } }
|
WithRouter
withRouter就可以使一般组件用上路由组件的API
withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter的返回值是一个新组件,里面有路由组件中含有的props属性。之后就可以像路由组件一样操作了
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("Header组件收到的props是", this.props); return ( <div className="page-header"> <h2>React Router Demo</h2> <button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> <button onClick={this.go}>go</button> </div> ); } }
export default withRouter(Header);
|
BrowserRouter 与 HashRouter 的区别
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。
(2).HashRouter刷新后会导致路由state参数的丢失!!!,但是如果不刷新只使用的话没有问题
4.备注:HashRouter可以用于解决一些路径错误相关的问题。
第6章:React UI组件库
6.1.流行的开源React UI组件库
6.1.1. material-ui(国外)
- 官网: http://www.material-ui.com/#/
2. github: https://github.com/callemall/material-ui
6.1.2. ant-design(国内蚂蚁金服)
- 官网: https://ant.design/index-cn
2. Github: https://github.com/ant-design/ant-design/
antd 的按需引入+自定义主题
1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader
2.修改package.json
"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject" },
|
3.根目录下创建config-overrides.js
const { override, fixBabelImports,addLessLoader} = require('customize-cra'); module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: true, }), addLessLoader({ lessOptions:{ javascriptEnabled: true, modifyVars: { '@primary-color': 'green' }, } }), );
|
4.备注:不用在组件里亲自引入样式了,即:import ‘antd/dist/antd.css’应该删掉
但是这些推荐查文档,但是文档可能会有些落后,到时候就只能查看lessLoader的文档了
第7章:redux
其实对标的就是Vue的vuex和pinia
7.1. redux理解
7.1.1. 学习文档
- 英文文档: https://redux.js.org/
2. 中文文档: http://www.redux.org.cn/
3. Github: https://github.com/reactjs/redux
7.1.2. redux是什么
- redux是一个专门用于做状态管理的JS库(不是react插件库)。
2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
3. 作用: 集中式管理react应用中多个组件共享的状态。
7.1.3. 什么情况下需要使用redux
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
2. 一个组件需要改变另一个组件的状态(通信)。
3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
7.1.4. redux工作流程

7.2. redux的三个核心概念
7.2.1. action
- 动作的对象
2. 包含2个属性
- type:标识属性, 值为字符串, 唯一, 必要属性
- data:数据属性, 值类型任意, 可选属性
- 例子:{ type: ‘ADD_STUDENT’,data:{name: ‘tom’,age:18} }
**7.2.2. reducer **
- 用于初始化状态、加工状态。
2. 加工时,根据旧的state和action, 产生新的state的纯函数。
7.2.3. store
- 将state、action、reducer联系在一起的对象
2. 如何得到此对象?
1) import {createStore} from ‘redux’
2) import reducer from ‘./reducers’
3) const store = createStore(reducer)
3. 此对象的功能?
1) getState(): 得到state
2) dispatch(action): 分发action, 触发reducer调用, 产生新的state
3) subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
7.3. redux的核心API
7.3.1. createstore()
作用:创建包含指定reducer的store对象
7.3.2. store对象
- 作用: redux库最核心的管理对象
2. 它内部维护着:
1) state
2) reducer
3. 核心方法:
1) getState()
2) dispatch(action)
3) subscribe(listener)
4. 具体编码:
1) store.getState()
2) store.dispatch({type:’INCREMENT’, number})
3) store.subscribe(render)
7.3.3. applyMiddleware()
作用:应用上基于redux的中间件(插件库)
7.3.4. combineReducers()
作用:合并多个reducer函数
7.4. 使用redux编写应用
** 效果**

精简版
(1).去除Count组件自身的状态
(2).src下建立:
-redux
-store.js
-count_reducer.js
(3).store.js:
1).引入redux中的createStore函数,创建一个store
2).createStore调用时要传入一个为其服务的reducer
3).记得暴露store对象
(4).count_reducer.js:
1).reducer的本质是一个函数,接收:preState,action,返回加工后的状态
2).reducer有两个作用:初始化状态,加工状态
3).reducer被第一次调用时,是store自动触发的,
传递的preState是undefined,
传递的action是:{type:‘@@REDUX/INIT_a.2.b.4}
(5).在index.js中监测store中状态的改变,一旦发生改变重新渲染
备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
目录结构

import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import store from "./redux/store";
ReactDOM.render(<App />, document.getElementById("root"));
store.subscribe(() => { ReactDOM.render(<App />, document.getElementById("root")); });
|
import React, { Component } from 'react' import Count from './components/Count'
export default class App extends Component { render() { return ( <div> <Count/> </div> ) } }
|
import { createStore } from "redux";
import countReducer from "./count_reducer";
export default createStore(countReducer);
|
const initState = 0; export default function countReducer(preState = initState, action) { const { type, data } = action; switch (type) { case "increment": return preState + data; case "decrement": return preState - data; default: return preState; } }
|
import React, { Component } from "react";
import store from "../../redux/store";
export default class Count extends Component { state = { carName: "奔驰c63" };
increment = () => { const { value } = this.selectNumber; store.dispatch({ type: "increment", data: value * 1 }); }; decrement = () => { const { value } = this.selectNumber; store.dispatch({ type: "decrement", data: value * 1 }); }; incrementIfOdd = () => { const { value } = this.selectNumber; const count = store.getState(); if (count % 2 !== 0) { store.dispatch({ type: "increment", data: value * 1 }); } }; incrementAsync = () => { const { value } = this.selectNumber; setTimeout(() => { store.dispatch({ type: "increment", data: value * 1 }); }, 500); };
render() { return ( <div> <h1>当前求和为:{store.getState()}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ); } }
|
完整版
新增文件:
1.count_action.js 专门用于创建action对象
2.constant.js 放置容易写错的type值
文件结构

export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
|
import { INCREMENT, DECREMENT } from "./constant";
export const createIncrementAction = (data) => ({ type: INCREMENT, data }); export const createDecrementAction = (data) => ({ type: DECREMENT, data });
|
import React, { Component } from "react"; import store from "../../redux/store";
import { createIncrementAction, createDecrementAction, } from "../../redux/count_action";
export default class Count extends Component {
increment = () => { const { value } = this.selectNumber; store.dispatch(createIncrementAction(value * 1)); }; decrement = () => { const { value } = this.selectNumber; store.dispatch(createDecrementAction(value * 1)); }; incrementIfOdd = () => { const { value } = this.selectNumber; const count = store.getState(); if (count % 2 !== 0) { store.dispatch(createIncrementAction(value * 1)); } }; incrementAsync = () => { const { value } = this.selectNumber; setTimeout(() => { store.dispatch(createIncrementAction(value * 1)); }, 500); };
render() { return ( <div> <h1>当前求和为:{store.getState()}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ); } }
|
其他的无变化
异步Action版
(1).明确:延迟的动作不想交给组件自身,想交给action
(2).何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
(3).具体编码:
1).yarn add redux-thunk,并配置在store中
2).创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
3).异步任务有结果后,分发一个同步的action去真正操作数据。
(4).备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。
import { createStore, applyMiddleware } from "redux";
import countReducer from "./count_reducer";
import thunk from "redux-thunk";
export default createStore(countReducer, applyMiddleware(thunk));
|
import { INCREMENT, DECREMENT } from "./constant";
export const createIncrementAction = (data) => ({ type: INCREMENT, data }); export const createDecrementAction = (data) => ({ type: DECREMENT, data });
export const createIncrementAsyncAction = (data, time) => { return (dispatch) => { setTimeout(() => { dispatch(createIncrementAction(data)); }, time); }; };
|
7.5. redux异步编程
上面的就是实例!
7.5.1理解:
- redux默认是不能进行异步处理的,
2. 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)
7.5.2. 使用异步中间件
npm install –save redux-thunk
7.6. react-redux

7.6.1. 理解
- 一个react插件库
2. 专门用来简化react应用中使用redux
7.6.2. react-Redux将所有组件分成两大类
- UI组件
1) 只负责 UI 的呈现,不带有任何业务逻辑
2) 通过props接收数据(一般数据和函数)
3) 不使用任何 Redux 的 API
4) 一般保存在components文件夹下
2. 容器组件
1) 负责管理数据和业务逻辑,不负责UI的呈现
2) 负责使用 Redux 的 API,和Redux进行通信
3) 一般保存在containers文件夹下
4)这个组件通过connect()()方法进行生成
7.6.3. 相关API
- Provider:让所有组件都可以得到state数据
2. connect:用于包装UI 组件生成容器组件
3. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性
4. mapDispatchToProps:将分发action的函数转换为UI组件的标签属性
(1).明确两个概念:
1).UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
2).容器组件:负责和redux通信,将结果交给UI组件。
(2).如何创建一个容器组件————靠react-redux 的 connect函数
connect(mapStateToProps,mapDispatchToProps)(UI组件)
-mapStateToProps:映射状态,返回值是一个对象
-mapDispatchToProps:映射操作状态的方法,返回值是一个对象
(3).备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
(4).备注2:mapDispatchToProps,也可以是一个对象
import React from 'react' import ReactDOM from 'react-dom' import App from './App' import store from './redux/store'
ReactDOM.render(<App/>,document.getElementById('root'))
|
import React, { Component } from "react"; import Count from "./containers/Count"; import store from "./redux/store";
export default class App extends Component { render() { return ( <div> {/* 给容器组件传递store 这样可以方便 容器组件 得到store中的state和dispatch */} <Count store={store} /> </div> ); } }
|
import CountUI from "../../components/Count";
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction, } from "../../redux/count_action";
import { connect } from "react-redux";
function mapStateToProps(state) { return { count: state }; }
function mapDispatchToProps(dispatch) { return { jia: (number) => dispatch(createIncrementAction(number)), jian: (number) => dispatch(createDecrementAction(number)), jiaAsync: (number, time) => dispatch(createIncrementAsyncAction(number, time)), }; }
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
|
import React, { Component } from 'react'
export default class Count extends Component {
state = {carName:'奔驰c63'}
increment = ()=>{ const {value} = this.selectNumber this.props.jia(value*1) } decrement = ()=>{ const {value} = this.selectNumber this.props.jian(value*1) } incrementIfOdd = ()=>{ const {value} = this.selectNumber if(this.props.count % 2 !== 0){ this.props.jia(value*1) } } incrementAsync = ()=>{ const {value} = this.selectNumber this.props.jiaAsync(value*1,500) }
render() { return ( <div> <h1>当前求和为:{this.props.count}</h1> <select ref={c => this.selectNumber = c}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } }
|
优化版
(1).容器组件和UI组件整合一个文件
(2).无需自己给容器组件传递store,给包裹一个即可。
(3).使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
(4).mapDispatchToProps也可以简单的写成一个对象
(5).一个组件要和redux“打交道”要经过哪几步?
(1).定义好UI组件—不暴露
(2).引入connect生成一个容器组件,并暴露,写法如下:
connect(
state => ({key:value}), //映射状态
{key:xxxxxAction} //映射操作状态的方法
)(UI组件)
(4).在UI组件中通过this.props.xxxxxxx读取和操作状态
import React, { Component } from 'react'
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/count_action'
import {connect} from 'react-redux'
class Count extends Component {
state = {carName:'奔驰c63'}
increment = ()=>{ const {value} = this.selectNumber this.props.jia(value*1) } decrement = ()=>{ const {value} = this.selectNumber this.props.jian(value*1) } incrementIfOdd = ()=>{ const {value} = this.selectNumber if(this.props.count % 2 !== 0){ this.props.jia(value*1) } } incrementAsync = ()=>{ const {value} = this.selectNumber this.props.jiaAsync(value*1,500) }
render() { return ( <div> <h1>当前求和为:{this.props.count}</h1> <select ref={c => this.selectNumber = c}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ) } }
export default connect( state => ({count:state}),
{ jia:createIncrementAction, jian:createDecrementAction, jiaAsync:createIncrementAsyncAction, } )(Count)
|
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import store from "./redux/store"; import { Provider } from "react-redux";
ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") );
|
数据共享版
目录结构

相关代码
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import store from "./redux/store"; import { Provider } from "react-redux";
ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") );
|
import React, { Component } from "react"; import Count from "./containers/Count"; import Person from "./containers/Person";
export default class App extends Component { render() { return ( <div> {/* 可以在这里引入多个组件 */} <Count /> <hr /> <Person /> </div> ); } }
|
import {INCREMENT,DECREMENT} from '../constant'
export const createIncrementAction = data => ({type:INCREMENT,data}) export const createDecrementAction = data => ({type:DECREMENT,data})
export const createIncrementAsyncAction = (data,time) => { return (dispatch)=>{ setTimeout(()=>{ dispatch(createIncrementAction(data)) },time) } }
|
import {ADD_PERSON} from '../constant'
export const createAddPersonAction = personObj => ({type:ADD_PERSON,data:personObj})
|
import { INCREMENT, DECREMENT } from "../constant";
const initState = 0; export default function countReducer(preState = initState, action) { const { type, data } = action; switch (type) { case INCREMENT: return preState + data; case DECREMENT: return preState - data; default: return preState; } }
|
import { ADD_PERSON } from "../constant";
const initState = [{ id: "001", name: "tom", age: 18 }];
export default function personReducer(preState = initState, action) { const { type, data } = action; switch (type) { case ADD_PERSON: return [data, ...preState]; default: return preState; } }
|
import { createStore, applyMiddleware, combineReducers } from "redux";
import countReducer from "./reducers/count";
import personReducer from "./reducers/person";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
const allReducer = combineReducers({ he: countReducer, rens: personReducer, });
export default createStore( allReducer, composeWithDevTools(applyMiddleware(thunk)) );
|
import React, { Component } from "react";
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction, } from "../../redux/actions/count";
import { connect } from "react-redux";
class Count extends Component { increment = () => { const { value } = this.selectNumber; this.props.jia(value * 1); }; decrement = () => { const { value } = this.selectNumber; this.props.jian(value * 1); }; incrementIfOdd = () => { const { value } = this.selectNumber; if (this.props.count % 2 !== 0) { this.props.jia(value * 1); } }; incrementAsync = () => { const { value } = this.selectNumber; this.props.jiaAsync(value * 1, 500); };
render() { return ( <div> <h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2> <h4>当前求和为:{this.props.count}</h4> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ); } }
export default connect( (state) => ({ count: state.he, renshu: state.rens.length, }), { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, } )(Count);
|
import React, { Component } from "react"; import { nanoid } from "nanoid"; import { connect } from "react-redux"; import { createAddPersonAction } from "../../redux/actions/person";
class Person extends Component { addPerson = () => { const name = this.nameNode.value; const age = this.ageNode.value * 1; const personObj = { id: nanoid(), name, age }; this.props.jiaYiRen(personObj); this.nameNode.value = ""; this.ageNode.value = ""; };
render() { return ( <div> <h2>我是Person组件,上方组件求和为{this.props.he}</h2> <input ref={(c) => (this.nameNode = c)} type="text" placeholder="输入名字" /> <input ref={(c) => (this.ageNode = c)} type="text" placeholder="输入年龄" /> <button onClick={this.addPerson}>添加</button> <ul> {this.props.yiduiren.map((p) => { return ( <li key={p.id}> {p.name}--{p.age} </li> ); })} </ul> </div> ); } }
export default connect( (state) => ({ yiduiren: state.rens, he: state.he }), { jiaYiRen: createAddPersonAction } )(Person);
|
运行效果

最终版
(1).所有变量名字要规范,尽量触发对象的简写形式。
(2).reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer
import {combineReducers} from 'redux'
import count from './count'
import persons from './person'
export default combineReducers({ count, persons })
|
7.7. 使用上redux调试工具
(1).yarn add redux-devtools-extension
(2).store中进行配置
import {composeWithDevTools} from ‘redux-devtools-extension’
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
7.7.1. 安装chrome浏览器插件

7.7.2. 下载工具依赖包
npm install –save-dev redux-devtools-extension
7.7.3. 配置
import { createStore, applyMiddleware, combineReducers } from "redux";
import countReducer from "./reducers/count";
import personReducer from "./reducers/person";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
const allReducer = combineReducers({ he: countReducer, rens: personReducer, });
export default createStore( allReducer, composeWithDevTools(applyMiddleware(thunk)) );
|
7.8. 纯函数和高阶函数
7.8.1. 纯函数
- 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
2. 必须遵守以下一些约束
1) 不得改写参数数据
2) 不会产生任何副作用,例如网络请求,输入和输出设备
3) 不能调用Date.now()或者Math.random()等不纯的方法
3. redux的reducer函数必须是一个纯函数
7.8.2. 高阶函数
- 理解: 一类特别的函数
1) 情况1: 参数是函数
2) 情况2: 返回是函数
2. 常见的高阶函数:
1) 定时器设置函数
2) 数组的forEach()/map()/filter()/reduce()/find()/bind()
3) promise
4) react-redux中的connect函数
3. 作用: 能实现更加动态, 更加可扩展的功能
扩展
1. setState
setState更新状态的2种写法
(1). setState(stateChange, [callback])——对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
(2). setState(updater, [callback])——函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
3.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,(因为react在更新state的时候是异步的), 要在第二个callback函数中读取
import React, { Component } from "react";
export default class Demo extends Component { state = { count: 0 };
add = () => { const { count } = this.state; this.setState({ count: count + 1 }, () => { console.log(this.state.count); }); console.log("12行的输出", this.state.count);
this.setState((state) => ({ count: state.count + 1 })); };
render() { return ( <div> <h1>当前求和为:{this.state.count}</h1> <button onClick={this.add}>点我+1</button> </div> ); } }
|
2. lazyLoad
路由组件的lazyLoad
一般用作路由组件的懒加载,平时的路由是只要启动的话就会全部加载,懒加载处理后就会用到哪个加载哪个
import React, { Component, lazy, Suspense } from "react"; import { NavLink, Route } from "react-router-dom";
import Loading from "./Loading";
const Home = lazy(() => import("./Home")); const About = lazy(() => import("./About"));
export default class Demo extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"> <h2>React Router Demo</h2> </div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 在React中靠路由链接实现切换组件--编写路由链接 */} <NavLink className="list-group-item" to="/about"> About </NavLink> <NavLink className="list-group-item" to="/home"> Home </NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 这里必须要指定这个标签,意思是下面路由组件正在加载的时候显示的组件 如果没有这个标签的话会报错 */} <Suspense fallback={<Loading />}> {/* 注册路由 */} <Route path="/about" component={About} /> <Route path="/home" component={Home} /> </Suspense> </div> </div> </div> </div> </div> ); } }
|
3. Hooks
1. React Hook/Hooks是什么?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
2. 三个常用的Hook
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect() 使用生命周期
(3). Ref Hook: React.useRef()
3. State Hook
(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)
(3). useState()说明:
参数: 第一次初始化指定的值在内部作缓存
返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:
setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
4. Effect Hook
(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3). 语法和说明:
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) //这里可以指定是检测哪个state的update操作。 如果指定的是[], 回调函数只会在第一次render()后执行
(4). 可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
5. Ref Hook
这个很像creatRef,专人专用
(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样
import React from "react"; import ReactDOM from "react-dom";
function Demo() { const [count, setCount] = React.useState(0); const myRef = React.useRef();
React.useEffect(() => { let timer = setInterval(() => { setCount((count) => count + 1); }, 1000); return () => { clearInterval(timer); }; }, []);
function add() { setCount((count) => count + 1); }
function show() { alert(myRef.current.value); }
function unmount() { ReactDOM.unmountComponentAtNode(document.getElementById("root")); }
return ( <div> <input type="text" ref={myRef} /> <h2>当前求和为:{count}</h2> <button onClick={add}>点我+1</button> <button onClick={unmount}>卸载组件</button> <button onClick={show}>点我提示数据</button> </div> ); }
export default Demo;
|
4. Fragment
jsx语法是不能出现多个根标签,Fragment可以做那个根标签,并且react在渲染真实dom的时候会直接忽略这个标签
这个很像vue里面的template标签,但是相比较template不能做根标签
使用
<></>
import React, { Component, Fragment } from "react";
export default class Demo extends Component { render() { return ( // <Fragment key={1}> // <input type="text"/> // <input type="text"/> // </Fragment> //这个和上面的区别只有上面可以传递key属性,其他的没什么区别 <> <input type="text" /> <input type="text" /> </> ); } }
|
这两个是差不多的,但是Fragment标签可以传输key属性
作用
可以不用必须有一个真实的DOM根标签了
5. Context
理解
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
也就是A和C、D间的通信
使用
1) 创建Context容器对象: const XxxContext = React.createContext()
2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据: <xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>
3) 后代组件读取数据:
static contextType = xxxContext this.context
<xxxContext.Consumer> { value => ( 要显示的内容 ) } </xxxContext.Consumer>
|
具体代码
import React, { Component } from "react"; import "./index.css";
const MyContext = React.createContext(); const { Provider, Consumer } = MyContext; export default class A extends Component { state = { username: "tom", age: 18 };
render() { const { username, age } = this.state; return ( <div className="parent"> <h3>我是A组件</h3> <h4>我的用户名是:{username}</h4> <Provider value={{ username, age }}> <B /> </Provider> </div> ); } }
class B extends Component { render() { return ( <div className="child"> <h3>我是B组件</h3> <C /> </div> ); } }
function C() { return ( <div className="grand"> <h3>我是C组件</h3> <h4> 我从A组件接收到的用户名: <Consumer>{(value) => `${value.username},年龄是${value.age}`}</Consumer> </h4> </div> ); }
|
注意
在应用开发中一般不用context, 一般都用它的封装react插件(react-redux)
6. 组件优化
Component的2个问题
- 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
- 只要当前组件重新render(), 就会自动重新render子组件(父组件render,子组件也得render),纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据(创建新对象)
项目中一般使用PureComponent来优化
import React, { Component } from 'react' import './index.css'
export default class Parent extends Component {
state = {carName:"奔驰c36"}
changeCar = ()=>{ this.setState({carName:'迈巴赫'}) }
shouldComponentUpdate(nextProps,nextState){ return !this.state.carName === nextState.carName }
render() { console.log('Parent---render'); const {carName} = this.state return ( <div className="parent"> <h3>我是Parent组件</h3> {this.state.stus} <span>我的车名字是:{carName}</span><br/> <button onClick={this.changeCar}>点我换车</button> <Child carName="奥拓"/> </div> ) } }
class Child extends Component {
shouldComponentUpdate(nextProps,nextState){ console.log(this.props,this.state); console.log(nextProps,nextState); return !this.props.carName === nextProps.carName }
render() { console.log('Child---render'); return ( <div className="child"> <h3>我是Child组件</h3> <span>我接到的车是:{this.props.carName}</span> </div> ) } }
|
import React, { PureComponent } from "react"; import "./index.css";
export default class Parent extends PureComponent { state = { carName: "奔驰c36"}; changeCar = () => { this.setState({carName:'迈巴赫'}) }; render() { console.log("Parent---render"); const { carName } = this.state; return ( <div className="parent"> <h3>我是Parent组件</h3> <span>我的车名字是:{carName}</span> <br /> <button onClick={this.changeCar}>点我换车</button> <Child carName="奥拓" /> </div> ); } }
class Child extends PureComponent { render() { console.log("Child---render"); return ( <div className="child"> <h3>我是Child组件</h3> <span>我接到的车是:{this.props.carName}</span> </div> ); } }
|
7. render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A> <B>xxxx</B> </A> {this.props.children} 问题: 如果B组件需要A组件内的数据, ==> 做不到
|
具体代码
import React, { Component } from "react"; import "./index.css";
export default class Parent extends Component { render() { return ( <div className="parent"> <h3>我是Parent组件</h3> <A> <B /> </A> </div> ); } }
class A extends Component { state = { name: "tom" }; render() { console.log(this.props); const { name } = this.state; return ( <div className="a"> <h3>我是A组件</h3> {/* 渲染B,但是这里没办法传递props */} {this.props.children()} </div> ); } }
class B extends Component { render() { console.log("B--render"); return ( <div className="b"> <h3>我是B组件</h3> </div> ); } }
|
render props
<A render={(data) => <C data={data}></C>}></A> A组件: {this.props.render(内部state数据)} C组件: 读取A组件传入的数据显示 {this.props.data}
|
具体代码
import React, { Component } from "react"; import "./index.css";
export default class Parent extends Component { render() { return ( <div className="parent"> <h3>我是Parent组件</h3> {/* 渲染A,并且把B作为A的子组件传入进去,并且可以把A的name属性传入进去 */} <A render={(name) => <B name={name} />} /> </div> ); } }
class A extends Component { state = { name: "tom" }; render() { console.log(this.props); const { name } = this.state; return ( <div className="a"> <h3>我是A组件</h3> {/* 在这里进行渲染B并且把A的name给传入进去 */} {this.props.render(name)} </div> ); } }
class B extends Component { render() { console.log("B--render"); return ( <div className="b"> {/* 接收到A的name属性 */} <h3>我是B组件,{this.props.name}</h3> </div> ); } }
|
8. 错误边界
开发环境还是会报错,生产环境不会报错
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
错误边界需要在容易发生错误的子组件的父组件做一些配置,如果子组件出现了错误,父组件仍然可以展示,并且会把子组件渲染成一个专门用于展示错误的组件,只把错误限制到子组件中,父组件仍然可以展示
特点:
只能捕获后代组件生命周期函数中产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
static getDerivedStateFromError(error) { console.log(error); return { hasError: true, }; }
componentDidCatch(error, info) { console.log(error, info); }
|
具体实例:
import React, { Component } from "react"; import Child from "./Child";
export default class Parent extends Component { state = { hasError: "", };
static getDerivedStateFromError(error) { console.log("@@@", error); return { hasError: error }; }
componentDidCatch() { console.log("此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决"); }
render() { return ( <div> <h2>我是Parent组件</h2> {/* 下面判断hasError是否为null或者是空字符串进行显示 */} {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />} </div> ); } }
|
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: "peiqi", age: 20 }, ], };
render() { return ( <div> <h2>我是Child组件</h2> {this.state.users.map((userObj) => { return ( <h4 key={userObj.id}> {userObj.name}----{userObj.age} </h4> ); })} </div> ); } }
|
9. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub
3.集中式管理:
redux
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
打包部署
npm run build 打包
npm i serve -g 安装serve
serve -s build 利用serve启动应用
React Router 6 快速上手

1.概述
- React Router 以三个不同的包发布到 npm 上,它们分别为:
- react-router: 路由的核心库,提供了很多的:组件、钩子。
- **react-router-dom(我们学这个):包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如 等 **。
- react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:等。
- 与React Router 5.x 版本相比,改变了什么?
- 内置组件的变化:移除 ,新增 等。
- 语法的变化:component={About} 变为 element={}等。
- 新增多个hook:useParams、useNavigate、useMatch等。
- 新增多个hook说明!官方明确推荐函数式组件了!!!……
安装react-router
npm i react-router-dom
2.Component
1.
- 说明:用于包裹整个应用。
- 示例代码:
import React from "react"; import ReactDOM from "react-dom"; import { BrowserRouter } from "react-router-dom";
ReactDOM.render( <BrowserRouter> {/* 整体结构(通常为App组件) */} </BrowserRouter>,root );
|
2.
- 说明:作用与一样,但修改的是地址栏的hash值。
- 备注:6.x版本中、 的用法与 5.x 相同。
3. 与
- v6版本中移出了先前的,引入了新的替代者:。
- 和 要配合使用,且必须要用包裹。
- 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。
- 属性用于指定:匹配时是否区分大小写(默认为 false)。
- 当URL发生变化时,都会查看其所有子 元素以找到最佳匹配并呈现组件 。
- 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 组件来渲染其子路由。
- 示例代码:
<Routes> <Route path="/login" element={<Login />}></Route>
<Route path="home" element={<Home />}> /*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/ <Route path="test1" element={<Test/>}></Route> <Route path="test2" element={<Test2/>}></Route> </Route>
<Route path="users"> <Route path="xxx" element={<Demo />} /> </Route> </Routes>
|
4.
- 作用: 修改URL,且不发送网络请求(路由链接)。
- 注意: 外侧需要用或包裹。
- 示例代码:
import { Link } from "react-router-dom";
function Test() { return ( <div> <Link to="/路径">按钮</Link> </div> ); }
|
5.
- 作用: 与组件类似,且可实现导航的“高亮”效果。
- 示例代码:
import React from "react"; import { NavLink, Routes, Route, Navigate } from "react-router-dom"; import About from "./pages/About"; import Home from "./pages/Home";
export default function App() { function computedClassName({ isActive }) { return isActive ? "list-group-item atguigu" : "list-group-item"; } return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"> <h2>React Router Demo</h2> </div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 路由链接 */} {/* className的值可以为一个函数, 这个函数写在了上面*/} <NavLink className={computedClassName} to="/about"> About </NavLink> <NavLink className={computedClassName} to="/home"> Home </NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Routes> <Route path="/ABOUT" element={<About />} /> <Route path="/home" element={<Home />} /> <Route path="/" element={<Navigate to="/about" />} /> </Routes> </div> </div> </div> </div> </div> ); }
|
6.
- 作用:只要组件被渲染,就会修改路径,切换视图。
- replace属性用于控制跳转模式(push 或 replace,默认是push)。
- 示例代码:
import React,{useState} from 'react' import {Navigate} from 'react-router-dom'
export default function Home() { const [sum,setSum] = useState(1) return ( <div> <h3>我是Home的内容</h3> {/* 根据sum的值决定是否切换视图,下面显示的是如果sum为1就显示h4标签,否则就跳转路由到about页面,并且为replace模式(与push相照应) */} {sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>} <button onClick={()=>setSum(2)}>点我将sum变为2</button> </div> ) }
|
还可以用来表示默认跳转到哪个页面
import React from "react"; import { NavLink, Routes, Route, Navigate } from "react-router-dom"; import About from "./pages/About"; import Home from "./pages/Home";
export default function App() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"> <h2>React Router Demo</h2> </div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 路由链接 */} <NavLink className="list-group-item" to="/about"> About </NavLink> <NavLink className="list-group-item" to="/home"> Home </NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} {/* Routes和Switch的区别是Switch是为了提升效率,可写可不写。 但是Routes必须得写,也能实现Switch的功能。 */} <Routes> <Route path="/about" element={<About />} /> {/* 默认不区分大小写 */} <Route path="/HOME" element={<Home />} /> {/* 这里表示如果路径是/,也就会默认重定向到about路径下,进行渲染About组件 */} <Route path="/" element={<Navigate to="/about" />} /> </Routes> </div> </div> </div> </div> </div> ); }
|
7.
- 当产生嵌套时,渲染其对应的后续子路由。
- 示例代码:

import About from "../pages/About"; import Home from "../pages/Home"; import Message from "../pages/Message"; import News from "../pages/News"; import { Navigate } from "react-router-dom";
export default [ { path: "/about", element: <About />, }, { path: "/home", element: <Home />, children: [ { path: "news", element: <News />, }, { path: "message", element: <Message />, }, ], }, { path: "/", element: <Navigate to="/about" />, }, ];
|
import React from "react"; import { NavLink, useRoutes } from "react-router-dom"; import routes from "./routes";
export default function App() { const element = useRoutes(routes); return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"> <h2>React Router Demo</h2> </div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 路由链接 */} <NavLink className="list-group-item" to="/about"> About </NavLink> {/* <NavLink className="list-group-item" end to="/home">Home</NavLink> 这里的意思是如果子路由被点击了,父路由的高亮就不显示了 */} <NavLink className="list-group-item" to="/home"> Home </NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 并且路由匹配的界面就显示在这里了 */} {element} </div> </div> </div> </div> </div> ); }
|
import React from "react"; import { NavLink, Outlet } from "react-router-dom";
export default function Home() { return ( <div> <h2>Home组件内容</h2> <div> <ul className="nav nav-tabs"> <li> {/* 注意, */} <NavLink className="list-group-item" to="news"> News </NavLink> </li> <li> <NavLink className="list-group-item" to="message"> Message </NavLink> </li> </ul> {/* 指定路由组件呈现的位置 */} <Outlet /> </div> </div> ); }
|
3.Hooks
1. useRoutes()
- 作用:根据路由表,动态创建和。
- 示例代码:
import About from '../pages/About' import Home from '../pages/Home' import {Navigate} from 'react-router-dom'
export default [ { path:'/about', element:<About/> }, { path:'/home', element:<Home/> }, { path:'/', element:<Navigate to="/about"/> } ]
import React from 'react' import {NavLink,useRoutes} from 'react-router-dom' import routes from './routes'
export default function App() { const element = useRoutes(routes) return ( <div> ...... {/* 注册路由 */} {element} ...... </div> ) }
|
真实项目使用:

import About from '../pages/About' import Home from '../pages/Home' import {Navigate} from 'react-router-dom'
export default [ { path:'/about', element:<About/> }, { path:'/home', element:<Home/> }, { path:'/', element:<Navigate to="/about"/> } ]
|
import React from 'react' import {NavLink,useRoutes} from 'react-router-dom' import routes from './routes'
export default function App() { const element = useRoutes(routes) return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 路由链接 */} <NavLink className="list-group-item" to="/about">About</NavLink> <NavLink className="list-group-item" to="/home">Home</NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} {element} </div> </div> </div> </div> </div> ) }
|
2. useNavigate()
- 作用:返回一个函数用来实现编程式导航。
- 示例代码:
import React, { useState } from "react"; import { Link, Outlet, useNavigate } from "react-router-dom";
export default function Message() { const navigate = useNavigate(); const [messages] = useState([ { id: "001", title: "消息1", content: "锄禾日当午" }, { id: "002", title: "消息2", content: "汗滴禾下土" }, { id: "003", title: "消息3", content: "谁知盘中餐" }, { id: "004", title: "消息4", content: "粒粒皆辛苦" }, ]);
function showDetail(m) { navigate("detail", { replace: false, state: { id: m.id, title: m.title, content: m.content, }, }); } return ( <div> <ul> {messages.map((m) => { return ( // 路由链接 <li key={m.id}> <Link to="detail" state={{ id: m.id, title: m.title, content: m.content, }} > {m.title} </Link> <button onClick={() => showDetail(m)}>查看详情</button> </li> ); })} </ul> <hr /> {/* 指定路由组件的展示位置 */} <Outlet /> </div> ); }
|
import React from "react"; import { useNavigate } from "react-router-dom";
export default function Header() { const navigate = useNavigate();
function back() { navigate(-1); } function forward() { navigate(1); }
return ( <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"> <h2>React Router Demo</h2> <button onClick={back}>←后退</button> <button onClick={forward}>前进→</button> </div> </div> ); }
|
3. useParams()
- 作用:回当前匹配路由的params参数,类似于5.x中的match.params。
- 示例代码:

import About from "../pages/About"; import Home from "../pages/Home"; import Message from "../pages/Message"; import News from "../pages/News"; import Detail from "../pages/Detail"; import { Navigate } from "react-router-dom";
export default [ { path: "/about", element: <About />, }, { path: "/home", element: <Home />, children: [ { path: "news", element: <News />, }, { path: "message", element: <Message />, children: [ { path: "detail/:id/:title/:content", element: <Detail />, }, ], }, ], }, { path: "/", element: <Navigate to="/about" />, }, ];
|
import React, { useState } from "react"; import { Link, Outlet } from "react-router-dom";
export default function Message() { const [messages] = useState([ { id: "001", title: "消息1", content: "锄禾日当午" }, { id: "002", title: "消息2", content: "汗滴禾下土" }, { id: "003", title: "消息3", content: "谁知盘中餐" }, { id: "004", title: "消息4", content: "粒粒皆辛苦" }, ]); return ( <div> <ul> {messages.map((m) => { return ( // 路由链接 <li key={m.id}> {/* 在这里进行传输参数 */} <Link to={`detail/${m.id}/${m.title}/${m.content}`}> {m.title} </Link> </li> ); })} </ul> <hr /> {/* 指定路由组件的展示位置 */} <Outlet /> </div> ); }
|
import React from "react"; import { useParams, useMatch } from "react-router-dom";
export default function Detail() { const { id, title, content } = useParams(); return ( <ul> <li>消息编号:{id}</li> <li>消息标题:{title}</li> <li>消息内容:{content}</li> </ul> ); }
|
4. useSearchParams()
- 作用:用于读取和修改当前位置的 URL 中的查询字符串。
- 返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
- 示例代码:

import About from "../pages/About"; import Home from "../pages/Home"; import Message from "../pages/Message"; import News from "../pages/News"; import Detail from "../pages/Detail"; import { Navigate } from "react-router-dom";
export default [ { path: "/about", element: <About />, }, { path: "/home", element: <Home />, children: [ { path: "news", element: <News />, }, { path: "message", element: <Message />, children: [ { path: "detail", element: <Detail />, }, ], }, ], }, { path: "/", element: <Navigate to="/about" />, }, ];
|
import React, { useState } from "react"; import { Link, Outlet } from "react-router-dom";
export default function Message() { const [messages] = useState([ { id: "001", title: "消息1", content: "锄禾日当午" }, { id: "002", title: "消息2", content: "汗滴禾下土" }, { id: "003", title: "消息3", content: "谁知盘中餐" }, { id: "004", title: "消息4", content: "粒粒皆辛苦" }, ]); return ( <div> <ul> {messages.map((m) => { return ( // 路由链接 <li key={m.id}> {/* 用search方式传输参数 */} <Link to={`detail?id=${m.id}&title=${m.title}&content=${m.content}`} > {m.title} </Link> </li> ); })} </ul> <hr /> {/* 指定路由组件的展示位置 */} <Outlet /> </div> ); }
|
import React from 'react' import {useSearchParams} from 'react-router-dom'
export default function Detail() { const [search,setSearch] = useSearchParams() const id = search.get('id') const title = search.get('title') const content = search.get('content') return ( <ul> <li>消息编号:{id}</li> <li>消息标题:{title}</li> <li>消息内容:{content}</li> </ul> ) }
|
5. useLocation()
- 作用:获取当前 location 信息,对标5.x中的路由组件的location属性。并且这里面可以获取state参数
- 示例代码:

import About from "../pages/About"; import Home from "../pages/Home"; import Message from "../pages/Message"; import News from "../pages/News"; import Detail from "../pages/Detail"; import { Navigate } from "react-router-dom";
export default [ { path: "/about", element: <About />, }, { path: "/home", element: <Home />, children: [ { path: "news", element: <News />, }, { path: "message", element: <Message />, children: [ { path: "detail", element: <Detail />, }, ], }, ], }, { path: "/", element: <Navigate to="/about" />, }, ];
|
import React, { useState } from "react"; import { Link, Outlet } from "react-router-dom";
export default function Message() { const [messages] = useState([ { id: "001", title: "消息1", content: "锄禾日当午" }, { id: "002", title: "消息2", content: "汗滴禾下土" }, { id: "003", title: "消息3", content: "谁知盘中餐" }, { id: "004", title: "消息4", content: "粒粒皆辛苦" }, ]); return ( <div> <ul> {messages.map((m) => { return ( // 路由链接 <li key={m.id}> {/* 这里进行传输数据 */} <Link to="detail" state={{ id: m.id, title: m.title, content: m.content, }} > {m.title} </Link> </li> ); })} </ul> <hr /> {/* 指定路由组件的展示位置 */} <Outlet /> </div> ); }
|
import React from "react"; import { useLocation } from "react-router-dom";
export default function Detail() { const { state: { id, title, content }, } = useLocation(); return ( <ul> <li>消息编号:{id}</li> <li>消息标题:{title}</li> <li>消息内容:{content}</li> </ul> ); }
|
6. useMatch()
- 作用:返回当前匹配信息,对标5.x中的路由组件的match属性。
- 示例代码:
<Route path="/login/:page/:pageSize" element={<Login />}/> <NavLink to="/login/1/10">登录</NavLink>
export default function Login() { const match = useMatch('/login/:x/:y') console.log(match) //输出match对象 //match对象内容如下: /* { params: {x: '1', y: '10'} pathname: "/LoGin/1/10" pathnameBase: "/LoGin/1/10" pattern: { path: '/login/:x/:y', caseSensitive: false, end: false } } */ return ( <div> <h1>Login</h1> </div> ) }
|
7. useInRouterContext()
作用:如果组件在 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。
App及其子组件都会处于上下文环境中
import React from "react"; import { useInRouterContext } from "react-router-dom";
export default function Demo() { console.log(useInRouterContext()); return <div>Demo</div>; }
|
8. useNavigationType()
- 作用:返回当前的导航类型(用户是如何来到当前页面的)。
- 返回值:POP、PUSH、REPLACE。
- 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。
9. useOutlet()
- 作用:用来呈现当前组件中渲染的嵌套路由。
- 示例代码:
const result = useOutlet() console.log(result)
|
10.useResolvedPath()
用来解析路径的。
作用:给定一个 URL值,解析其中的:path、search、hash值。

