react
# 第一章. React入门
# 1.1. React的基本认识
1). Facebook开源的一个js库
2). 一个用来动态构建用户界面的js库
3). React的特点
Declarative(声明式编码)
Component-Based(组件化编码)
Learn Once, Write Anywhere(支持客户端与服务器渲染)
高效
单向数据流
4). React高效的原因
虚拟(virtual)DOM, 不总是直接操作DOM(批量更新, 减少更新的次数)
高效的DOM Diff算法, 最小化页面重绘(减小页面更新的区域)
One Dark Pro
Auto Rename Tag
Bracket Pair Colorizer 2
# 1.2. React的基本使用
1). 导入相关js库文件(react.js, react-dom.js, babel.min.js)
2). 编码:
<div id="app"></div>
<script type="text/javascript">
let vDom = 'hello'
ReactDOM.render(vDom,document.getElementById("app"))
</script>
2
3
4
5
6
7
# 1.3. JSX的理解和使用
1). 理解
* 全称: JavaScript XML
* react定义的一种类似于XML的JS扩展语法: XML+JS
* 作用: 用来创建react虚拟DOM(元素)对象
2). 编码相关
<div id="test"></div>
---------------------------------------
<script type="text/babel"> //类型为text/babel
let myID = 'id'
let myData = 'hello,world'
//1.创建虚拟DOM
let vDOM2 = <h2 id={myID}><span>{myData}</span></h2>
//2.渲染虚拟DOM到页面
ReactDOM.render(vDOM2,document.getElementById('test'))
</script>
3). 注意:
(1)标签的class属性必须改为className属性
(2)必须要有根标签
(3)标签中js语法用{}包起来,{}中的js必须是表达式
(4)标签必须有结束
(5)标签的style属性值必须为: {{color:'red', width:12}}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1.4概念理解
# 1). 模块与组件
1. 模块:
理解: 向外提供特定功能的js程序, 一般就是一个js文件
为什么: js代码更多更复杂
作用: 复用js, 简化js的编写, 提高js运行效率
2. 组件:
理解: 用来实现特定界面功能效果的代码集合(html/css/js/img)
为什么: 一个界面的功能太复杂了
作用: 复用编码, 简化项目界面编码, 提高运行效率
# 2). 模块化与组件化
1. 模块化:
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
2. 组件化:
当应用是以多组件的方式实现功能, 这上应用就是一个组件化的应用
# 第二章. react组件化开发
# 2.1. 基本理解和使用
//组件名首字母必须大写
1). 创建组件类
//方式1: 工厂函数(简单组件, 推荐使用)
function MyComponent1() {
return <h1>工厂函数</h1>
}
//方式2: ES6类语法(复杂组件, 推荐使用)
class MyComponent2 extends React.Component {
render () {
return <h1>ES6类语法</h1>
}
}
2). 渲染组件标签
ReactDOM.render(<MyComponent1/>, document.getElementById("app"))
ReactDOM.render(<MyComponent2/>, document.getElementById("app"))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.2. 组件的3大属性: state
(所有属性针对的是组件实例对象)
1. 组件被称为"状态机", 页面的显示是根据组件的state属性的数据来显示
2. 初始化指定:
state = {isPig:true} //state以键值对的形式存放数据
3. 读取显示:
let {isPig}=this.state //结构赋值形式取state值
4. 更新状态-->更新界面 :
this.setState({isPig : false}) //以键值对形式
2
3
4
5
6
7
实例代码
class Person extends React.Component {
state = { isP: true };
demo = () => { //自定义的方法必须用馒头函数,可以解决this问题
let isP = !this.state.isP;
this.setState({ isP }); //state不能直接更新,通过setState更新
};
render() {
let { isP } = this.state;
//React中的事件名需要大写,需要用this
return <h1 onClick={this.demo}>你是{isP ? "人" : "猪"}</h1>;
}
}
ReactDOM.render(<Person />, document.getElementById("test"));
2
3
4
5
6
7
8
9
10
11
12
13
注意事项
//1.state中的数据不能直接修改
this.state=!this.state //错误做法
this.setState({}) //正确
//2.如果state中的数据是对象类型,不能直接更新状态
let {comment}=this.state //错误,修改了原数组中对象
let comment=[...this.state.comment] //正确,使用es8的...运算符,创建了一个新的数组
2
3
4
5
6
# 2.2. 组件的3大属性: props
所有组件标签的属性的集合对象
给标签指定属性, 保存外部数据(可能是一个function)
在组件内部读取属性: this.props.propertyName
作用: 从目标组件外部向组件内部传递数据
对props中的属性值进行类型限制和必要性限制
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
扩展属性: 将对象的所有属性通过props传递
<Person {...person}/>
实例对象
class Person extends React.Component{
//类里面的方法一旦加上static关键字,该方法只能有类本身调用,类的实例对象不能使用。
//限制接收参数的类型以及必要性
static propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string.isRequired,
age:PropTypes.number
}
//设置参数的默认值
static defaultProps = {
age:18
}
//重写父类的render方法
render(){
let {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{age+1}</li>
<li>年龄:{sex}</li>
<hr/>
</ul>
)
}
}
let p2 = {
name:'张三',
age:19,
sex:'男'
}
//2.渲染组件标签,利用react内部的...obj属性获取组件
ReactDOM.render(<Person {...p2}/>,document.getElementById('example2'))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
组件中的props
//定义状态
state={
comment:[
{id:"ggttjjja",name:"jack",content:"简单"},
{id:"dddghhja",name:"bar",content:"不难"},
{id:"dsadwt03",name:"foo",comment:"so easy"}
]
}
//List传参
<List comment={comment}/>
-------------------------
//接收参数
let { comment } = this.props
2
3
4
5
6
7
8
9
10
11
12
13
# 2.2. 组件的3大属性: refs
作用: 找到组件内部的真实dom元素对象, 进而操作它
三种使用方式
方式一:ref="input1" //字符串形式
方式二:ref={input => this.input1 = input}//回调函数形式
方式三:myRef = React.createRef()//创建一个ref容器形式
2
3
4
5
示例代码:方式一
class UserInput extends React.Component {
handlerClick = () => {
//通过ref获取DOM
let { input1 } = this.refs;
alert(input1.value);
};
render() {
return (
<div>
//方式一:
<input type="text" ref="input1" />
<button onClick={this.handlerClick}>弹出数据</button>
</div>
);
}
}
ReactDOM.render(<UserInput />, document.getElementById("example"));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
方式二
class UserInput extends React.Component {
handlerClick = () => {
//通过ref获取DOM
alert(this.input1.value);
};
render() {
return (
<div>
{/* 方式二 inptu为形参,自定义,inptut1为方法名 */}
<input type="text" ref={(input) => (this.input1 = input)} />
<button onClick={this.handlerClick}>弹出数据</button>
</div>
);
}
}
ReactDOM.render(<UserInput />, document.getElementById("example"));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
方式三
class UserInput extends React.Component {
//方式三:创建一个ref容器
//这个容器是一个“专用容器”,只能保存一个元素
myRef = React.createRef();
handlerClick = () => {
//通过ref获取DOM,需要通过current属性获取value
let { current } = this.myRef;
alert(current.value);
};
render() {
return (
<div>
{/*将当前元素放入组件对象身上的myRef容器中*/}
<input type="text" ref={this.myRef} />
<button onClick={this.handlerClick}>弹出数据</button>
</div>
);
}
}
ReactDOM.render(<UserInput />, document.getElementById("example"));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.3. 组件中的事件处理
1. 给标签添加属性: onXxx={this.eventHandler}
2. 在组件中添加事件处理方法
eventHandler = (event) => {
}
3. 使自定义方法中的this为组件对象
在constructor()中bind(this)
使用箭头函数定义方法
4. 事件监听
绑定事件监听
事件名
回调函数
触发事件
用户对对应的界面做对应的操作
编码
5.注意:在调用函数是需要传参时要放在函数里面
onXxx={()=>{this.eventHandler(id)}}
eventHandler = (id) => {
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.4. 组件的组合使用
1)拆分组件: 拆分界面,抽取组件
2)实现静态组件: 使用组件实现静态页面效果
3)实现动态组件
① 动态显示初始化数据
② 交互功能(从绑定事件监听开始)
# 2.5. 组件收集表单数据
受控组件: 输入过程中自动收集数据到state
非受控组件: 提交时手动读取数据
# 2.6. 组件的生命周期(旧)
1. 组件的三个生命周期状态:
Mount:插入真实 DOM
Update:被重新渲染
Unmount:被移出真实 DOM
2. 生命周期流程:
* 第一次初始化显示: ReactDOM.render(<Xxx/>, containDom)
constructor()
componentWillMount() : 将要插入回调
render() : 用于插入虚拟DOM回调
componentDidMount() : 已经插入回调
* 每次更新state: this.setState({})
componentWillReceiveProps(): 接收父组件新的属性
componentWillUpdate() : 将要更新回调
render() : 更新(重新渲染)
componentDidUpdate() : 已经更新回调
* 删除组件: ReactDOM.unmountComponentAtNode(div): 移除组件
componentWillUnmount() : 组件将要被移除回调
3. 常用的方法
componentDidMount() : 只执行一次, 已经在dom树中, 适合启动/设置一些监听
componentWillUnmount():适合做收尾工作,清楚定时器/关闭数据库/关闭流
# 2.7组件的生命周期(新)
组件的三个生命周期状态:
* 初始化阶段:第一次渲染,插入真实DOM
* 更新阶段:重新渲染
* 卸载阶段:被移出真实 DOM
- 生命周期流程:
a. 初始化阶段: 由ReactDOM.render()触发
* constructor()
* static getDerivedStateFromProps()
* render()
* componentDidMount()
b. 更新阶段 由组件内部this.setSate()或父组件重新render触发
* static getDerivedStateFromProps()
* shouldComponentUpdate()
* render()
* getSnapshotBeforeUpdate()
* componentDidUpdate()
c. 移除组件: 由ReactDOM.unmountComponentAtNode(containerDom)触发
* componentWillUnmount()
重要的勾子
render(): 初始化渲染或更新渲染调用
componentDidMount(): 开启监听, 发送ajax请求
componentWillUnmount(): 做一些收尾工作, 如: 清理定时器
static getDerivedStateFromProps(): state是根据props来生成的那就需要借助这个函数
# 2.7. 虚拟DOM与DOM diff算法
# 1). 虚拟DOM是什么?
一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)
虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
如果只是更新虚拟DOM, 页面是不会重绘的
# 2). 虚拟DOM 算法的基本步骤
用JS对象树表示DOM树的结构;然后用这个树构建一个真正的DOM树插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把差异应用到真实DOM树上,视图就更新了
# 3). 进一步理解
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
# 第3章:react应用(基于react脚手架)
# 3.1. 使用create-react-app创建react应用
# 3.1.1. react脚手架
xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
a. 包含了所有需要的配置
b. 指定好了所有的依赖
c. 可以直接安装/编译/运行一个简单效果
react提供了一个用于创建react项目的脚手架库: create-react-app
项目的整体技术架构为: react + webpack + es6 + eslint
使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
# 3.1.2. 创建项目并启动
npm install -g create-react-app //全局下载react脚手架
create-react-app hello-react //创建react项目,hello-react为项目名
cd hello-react //切换到项目目录
npm start //运行项目
2
3
4
5
6
7
# 3.1.3. react脚手架项目结构
ReactNews
|--node_modules---第三方依赖模块文件夹
|--public
|-- *index.html-----------------*主页面
|--src------------源码文件夹
|--components-----------------react**组件
|--index.js-------------------应用入口js
|--.gitignore------git版本管制忽略的配置
|--package.json----应用包配置文件
|--README.md-------应用描述说明的readme文件
# 第4章:react ajax
# 4.1. 理解
# 4.1.1. 前置说明
React本身只关注于界面, 并不包含发送ajax请求的代码
前端应用需要通过ajax请求与后台进行交互(json数据)
react应用中需要集成第三方ajax库(或自己封装)
# 4.1.2. 常用的ajax请求库
jQuery: 比较重, 如果需要另外引入不建议使用
axios: 轻量级, 建议使用
a. 封装XmlHttpRequest对象的ajax
b. promise风格
c. 可以用在浏览器端和node服务器端
fetch: 原生函数, 但老版本浏览器不支持
a. 不再使用XmlHttpRequest对象提交ajax请求
b. 为了兼容低版本的浏览器, 可以引入兼容库fetch.js
# 4.1.3. 效果
需求:
1. 界面效果如下
2. 根据指定的关键字在github上搜索匹配的最受关注的库
3. 显示库名, 点击链接查看库
4. 测试接口: https://api.github.com/search/repositories?q=r&sort=stars
2
3
4
5
6
# 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);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST请求
axios
.post("/user", { firstName: "Fred", lastName: "Flintstone" })
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
2
3
4
5
6
7
8
9
# 4.3. Fetch
# 4.3.1. 文档
https://github.github.io/fetch/
https://segmentfault.com/a/1190000003810652
# 4.3.2. 相关API
GET请求
fetch(url)
.then(function (response) {
return response.json(); //返回一个promise实例对象,状态为<pending>
})
.then(function (value) {
console.log(value); //成功
})
.catch(function (reason) {
console.log(e); //失败
});
2
3
4
5
6
7
8
9
10
POST请求
fetch(url, { method: "POST", body: JSON.stringify(data) })
.then(function (response) {
return response.json(); //返回一个promise实例对象,状态为<pending>
})
.then(function (value) {
console.log(value); //成功
})
.catch(function (reason) {
console.log(e); //失败
});
2
3
4
5
6
7
8
9
10
fetch的问题
url: https://api.github.com/search/repositories
.then(function (response) {
return response.json(); //在此方法中如果https://api.github.com有错误,调用catch,但是search/repositories有错误,则调用的是then,显然不能得到数据。解决方案:自己简单对response进行封装
})
2
3
4
# 第5章:几个重要技术总结
# 5.1. 组件间通信
# 5.1.1. 方式一: 通过props传递
共同的数据放在父组件上, 特有的数据放在自己组件内部(state)
通过props可以传递一般属性和函数属性, 只能一层一层传递
一般属性-->父组件传递数据给子组件-->子组件读取数据
函数属性-->子组件传递数据给父组件-->子组件调用函数
# 5.1.2. 方式二: 使用消息订阅(subscribe)-发布(publish)机制
1) 工具库: PubSubJS
2) 下载: npm install pubsub-js
3) 使用:
import PubSub from 'pubsub-js' //引入
PubSub.subscribe('delete', (msg,data)=>{}); //订阅消息,一般放在钩子里,delete是标识,与发布相匹配,data是真正数据
PubSub.publish('delete', data) //发布消息,data是发布的数据,与上面的相对应
2
3
4
5
6
7
8
9
10
11
示例代码:
//List组件,订阅消息
componentDidMount(){
Pubsub.subscribe('hello',(msg,data)=>{
this.setState(data)
})
}
//Add组件,发布消息
Pubsub.publish('hello',{
users:[],
isFirst:false,
isLoading:true,
error:""
})
2
3
4
5
6
7
8
9
10
11
12
13
# 5.1.3. 方式三: redux
后面专门讲解
# 5.1.4. 面试题: 比较react中组件间3种通信方式
*1).* *方式一**:* *通过**props**传递 通过**props**可以传递一般数据和函数数据**, * *一般数据**-->**父组件向子组件 函数数据**-->**子组件向父组件通信 缺点**:* *只能一层一层传递**/**兄弟组件必须借助父组件 * *2).* *方式二**:* *使用消息订阅**(subscribe)-**发布**(publish)**机制 实现库**: pubsub-js * *组件**A:* *发布消息**(**相当于触发事件**) * *组件**B:* *订阅消息**,* *接收消息**,* *处理消息**(**相当于绑定事件监听**) * *优点**:* *对组件关系没有限制 * *3).* *方式三**: redux * *通过**redux**可以实现任意组件间的通信 集中式管理多个组件共享的状态**,* *而**pubsub-js**并不是集中式的管理*
# 5.2. 事件监听理解
# 5.2.1. 原生DOM事件
绑定事件监听
a. 目标元素
b. 事件名(类型): 只有有限的几个, 不能随便写
c. 回调函数: 接收数据并处理
触发事件
a. 用户操作界面
b. 事件名(类型)
c. 数据(event)
# 5.2.2. 自定义事件(消息机制)
绑定事件监听(订阅消息)
a. 目标元素
b. 事件名(类型): 任意
c. 回调函数: 通过形参接收数据, 在函数体处理事件
触发事件(编码, 发布消息)
a. (事件名类型): 与绑定的事件监听的事件名一致
b. 数据: 会自动传递给回调函数
# 5.3. ES6常用新语法
定义常量/变量: const/let
解构赋值: let {a, b} = this.props import {aa} from 'xxx' function fn({name}) {} fn({name: ‘tom’})
对象的简洁表达: {a, b}
箭头函数:
a. 常用场景
* 组件的自定义方法: xxx = () => {}
* 参数匿名函数
b. 优点:
* 简洁
* 没有自己的this,使用引用this查找的是外部this
扩展(三点)运算符: 拆解对象(const MyProps = {}, <Xxx {...MyProps}>)
类: class/extends/constructor/super
ES6模块化: export default | import
promise
async/await
# 第6章:react-router4
# 6.1. 相关理解
# 6.1.1. react-router-dom的理解
react的一个插件库
专门用来实现一个SPA应用
基于react的项目基本都会用到此库
# 6.1.2. SPA的理解
单页Web应用(single page web application,SPA)
整个应用只有一个完整的页面
点击页面中的链接不会刷新页面, 本身也不会向服务器发请求
当点击路由链接时, 只会做页面的局部更新
数据都需要通过ajax请求获取, 并在前端异步展现
# 6.1.3. 路由的理解
什么是路由?
a. 一个路由就是一个映射关系(key:value)
b. key为路由路径, value可能是function/component
路由分类
a. 后台路由: node服务器端路由, value是function, 用来处理客户端提交的请求并返回一个响应数据
b. 前台路由: 浏览器端路由, value是component, 当请求的是路由path时, 浏览器端前没有发送http请求, 但界面会更新显示对应的组件
后台路由
a. 注册路由: router.get(path, function(req, res))
b. 当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由
a. 注册路由:
b. 当浏览器的hash变为/about时, 当前路由组件就会变为About组件
# 6.1.4. 前端路由的实现
history库
a. 网址: https://github.com/ReactTraining/history
b. 管理浏览器会话历史(history)的工具库
c. 包装的是原生BOM中window.history和window.location.hash
history API
a. History.createBrowserHistory(): 得到封装window.history的管理对象
b. History.createHashHistory(): 得到封装window.location.hash的管理对象
c. history.push(): 添加一个新的历史记录
d. history.replace(): 用一个新的历史记录替换当前的记录
e. history.goBack(): 回退到上一个历史记录
f. history.goForword(): 前进到下一个历史记录
g. history.listen(function(location){}): 监视历史记录的变化
# 6.2. react-router相关API
# 6.2.1. 组件
<BrowserRouter>
<HashRouter>
<Route>
<Redirect>
<Link>
<NavLink>
<Switch>
# 6.2.2. 其它
history对象
match对象
withRouter函数
# 6.3. 基本路由使用
# 6.3.1. 准备
yarn add react-router-dom // 下载react-router-dom
# 6.3.2. 路由组件: pages/about.jsx
import React, { Component } from "react";
class About extends Component {
render() {
return (
<h3>我是About的内容</h3>
);
}
}
export default About;
2
3
4
5
6
7
8
9
10
# 6.3.3. 路由组件: pages/home.jsx
import React, { Component } from "react";
class Home extends Component {
render() {
return (
<h3>我是Home的内容</h3>
);
}
}
export default Home;
2
3
4
5
6
7
8
9
10
# 6.3.4.BrowserRouter
import {BrowserRouter} from 'react-router-dom'
<BrowserRouter>
<App />
</BrowserRouter>
//需要在index.js里面的App组件外面加上BrowserRouter才能使得NavLink生效
注意:在引入要样式时不要前面的点或者写成%PUBLIC_URL%这样形式的
<link rel="stylesheet" href="%PUBLIC_URL%/bootstrap.css" />
2
3
4
5
6
7
# 6.3.5. 应用组件:app.jsx
import React, { Component } from "react";
import { NavLink, Redirect, Route, Switch } from "react-router-dom";
import About from './pages/about'
import Home from './pages/home'
<div className="list-group">
<NavLink className="list-group-item" to="/about" activeClassName="demo">About</NavLink>
<NavLink className="list-group-item" to="/home" activeClassName="demo">Home</NavLink> </div>
//NavLink为路由链接,to是跳转到地方,前面需要加/
//activeClassName是选中时的样式
--------------------------------------------
<Switch>
<Route path="/about" component={About}></Route>
<Route path="/home" component={Home}></Route>
<Redirect to="/about"></Redirect>
</Switch>
//Switch是避免路由一直匹配,当匹配到路由时就停止
//Route是路由,path是匹配的路由路径
//Redirect是重定向,当不指定路由是时,默认的为Redirect
//component={About}引入组件的另一种方式
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 6.3.6. 自定义样式: index.css
.demo {
background-color: gray !important;
color: orange !important;
}
2
3
4
# 6.4. 嵌套路由使用
# 6.4.2. 二级路由组件: pages/news.jsx
import React, { Component } from "react";
class HomeNews extends Component {
render() {
return (
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
);
}
}
export default HomeNews;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6.4.3. 二级路由组件: pages/message.jsx
import React, { Component } from "react";
class HomeMessage extends Component {
render() {
return (
<div>
<ul>
<li>
<button>replace查看</button>
</li>
</ul>
</div>
);
}
}
export default HomeMessage;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6.4.4. 一级路由组件: pages/home.jsx
import React, { Component } from "react";
import { NavLink, Redirect, Route, Switch } from "react-router-dom";
import HomeNews from './home_news'
import HomeMessage from './home_message'
class Home extends Component {
render() {
return (
<div><h2>Home组件内容</h2>
<div>
<ul className="nav nav-tabs">
<li>
<NavLink className="list-group-item" to="/home/news" activeClassName="selected">News</NavLink>
</li>
<li>
<NavLink className="list-group-item " to="/home/message" activeClassName="selected">Message</NavLink>
</li>
</ul>
<Switch>
<Route path="/home/news" component={HomeNews}></Route>
<Route path="/home/message" component={HomeMessage}></Route>
<Redirect to="/home/news"></Redirect>
</Switch>
</div>
</div>
);
}
}
export default Home;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 6.5. 向路由组件传递参数数据
# 6.5.1. 三级路由组件: pages/home-message-detail.jsx
import React, { Component } from 'react'
export default class HomeMessageDetail extends Component {
state = {
messageDetail: [
{ id: 1, title: '消息1', content: '内容1' },
{ id: 2, title: '消息2', content: '内容2' },
{ id: 3, title: '消息3', content: '内容3' },
]
}
render() {
// console.log('我是hmd的实例',this.props.match.params);
let { id } = this.props.match.params
//通过组件实例对象this.props.match.params上取得参数(id)
//注意:通过实例取得的参数是字符串,需要转换为Number类型与之匹配
let { messageDetail } = this.state
let obj = messageDetail.find((item) => {
return item.id === id * 1
})
if (obj) {
return (
<ul>
<li>id:{obj.id}</li>
<li>title:{obj.til}</li>
<li>content:{obj.content}</li>
</ul>
)
} else {
return <h1>没有消息</h1>
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 6.5.2. 二级路由组件: pages/home-message.jsx
import React, { Component } from "react";
import { Link, Route } from "react-router-dom"
import HomeMessageDetail from './home_message_detail'
class HomeMessage extends Component {
state = {
message: []
}
componentDidMount() {
setInterval(() => {
this.setState({
message: [
{ id: 1, title: "message1" },
{ id: 2, title: "message2" },
{ id: 3, title: "message3" }
]
})
}, 1000)
}
render() {
let { message } = this.state
return (
<div>
<ul>
{message.map((item) => {
return (
<li key={item.id}>
<Link to={`/home/message/detail/${item.id}`}>{item.title}</Link>
//在/home/message/detail/后面传递参数
</li>
)
})
}
</ul>
<hr />
<Route path="/home/message/detail/:id" component={HomeMessageDetail}></Route>
//在/home/message/detail/后面通过占位符接收参数(一般为id)
</div>
);
}
}
export default HomeMessage;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 6.6. 多种路由跳转方式
# 6.6.1. 二级路由: views/message.jsx
import React, { Component } from "react";
import { Link, Route } from "react-router-dom"
import HomeMessageDetail from './home_message_detail'
class HomeMessage extends Component {
state = {
message: []
}
componentDidMount() {
setInterval(() => {
this.setState({
message: [
{ id: 1, title: "message1" },
{ id: 2, title: "message2" },
{ id: 3, title: "message3" }
]
})
}, 1000)
}
push = (id) => {
this.props.history.push(`/home/message/detail/${id}`)
}
replace = (id) => {
this.props.history.replace(`/home/message/detail/${id}`)
}
render() {
let { message } = this.state
return (
<div>
<ul>
{message.map((item) => {
return (
<li key={item.id}>
<Link to={`/home/message/detail/${item.id}`}>{item.title}</Link>
<button onClick={() => { this.push(item.id) }}>push</button>
<button onClick={() => { this.replace(item.id) }}>replace</button>
//通过button方式跳转路由,原理就是借助History库通过改变改变地址栏中的url来实现
</li>
)
})
}
</ul>
<hr />
<Route path="/home/message/detail/:id" component={HomeMessageDetail}></Route>
</div>
);
}
}
export default HomeMessage;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 第7章:react UI组件库
# 7.1. 最流行的开源React UI组件库
# 7.1.1. material-ui(国外)
官网: http://www.material-ui.com/#/
github: https://github.com/callemall/material-ui
# 7.1.2. ant-design(国内蚂蚁金服)
PC官网: https://ant.design/index-cn
移动官网: https://mobile.ant.design/index-cn
Github: https://github.com/ant-design/ant-design/
Github: https://github.com/ant-design/ant-design-mobile/
# 7.2. ant-design使用入门
# 1.引入antd
参考文档:
https://ant.design/docs/react/use-with-create-react-app-cn
# 2.下载组件库包
yarn add antd
# 3.实现组件的按需打包
- 下载依赖模块
yarn add react-app-rewired customize-cra babel-plugin-import
- 在根目录下创建加载配置的js模块: config-overrides.js
const {override, fixBabelImports} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
2
3
4
5
6
7
8
3) 修改配置: package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
2
3
4
5
6
# 4. 自定义antd主题
- 下载工具包:
yarn add less@3.9.0 less-loader@4.1.0 //less版本和less-loader需要匹配
- 修改config-overrides.js
const {override, fixBabelImports, addLessLoader} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: {'@primary-color': '#1DA57A'},
})
);
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5.在应用组件中使用antd
import { Button } from 'antd';
render() {
return (
<div>
App
<Button type="primary">Button</Button>
</div>
);
}
2
3
4
5
6
7
8
9
# 第8章 Redux
# 8.1. redux理解
# 8.1.1. 学习文档
英文文档: https://redux.js.org/
中文文档: http://www.redux.org.cn/
Github: https://github.com/reactjs/redux
# 8.1.2. redux是什么?
redux是一个独立专门用于做状态管理的JS库(不是react插件库)
它可以用在react, angular, vue等项目中, 但基本与react配合使用
作用: 集中式管理(读/写)react应用中多个组件共享的状态
# 8.1.3. 什么情况下需要使用redux
总体原则: 能不用就不用, 如果不用比较吃力才考虑使用
某个组件的状态,需要共享
某个状态需要在任何地方都可以拿到
一个组件需要改变全局状态
一个组件需要改变另一个组件的状态
# 8.2. redux的核心API
# 8.2.1. createStore()
作用:
创建包含指定reducer的store对象
编码:
import {createStore} from 'redux'
import counter from './reducers/counter'
const store = createStore(counter)
# 8.2.2. store对象
作用:
redux库最核心的管理对象
它内部维护着:
state
reducer
核心方法:
getState()
dispatch(action)
subscribe(listener)
编码:
store.getState()
store.dispatch({type:'INCREMENT', number})
store.subscribe(render)
# 8.2.3. applyMiddleware()
作用:
应用上基于redux的中间件(插件库)
编码:
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk' // redux异步中间件
const store = createStore(
counter,
applyMiddleware(thunk) // 应用上异步中间件
)
# 8.2.4. combineReducers()
作用:
合并多个reducer函数
编码:
export default combineReducers({
user,
chatUser,
chat
})
# 8.3. redux的三个核心概念
# 8.3.1. action
标识要执行行为的对象
包含2个方面的属性
a. type: 标识属性, 值为字符串, 唯一, 必要属性
b. xxx: 数据属性, 值类型任意, 可选属性
例子:
const action = {
type: 'INCREMENT',
data: 2
}
Action Creator(创建Action的工厂函数)
const increment = (number) => ({type: 'INCREMENT', data: number})
# 8.3.2. reducer
根据老的state和action, 产生新的state的纯函数
样例
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + action.data
case 'DECREMENT':
return state - action.data
default:
return state
}
}
注意
a. 返回一个新的状态
b. 不要修改原来的状态
# 8.3.3. store
将state,action与reducer联系在一起的对象
如何得到此对象?
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
此对象的功能?
getState(): 得到state
dispatch(action): 分发action, 触发reducer调用, 产生新的state
subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
# 8.4. 使用redux编写应用
# 8.4.1. 效果
# 8.4.2. 下载依赖包
npm install --save redux
# 8.4.3. redux/action_types.js
*/\* * *a**ction**对象的**type**常量名称模块** \*/ * **export const** INCREMENT = **'increment' ** **export const** DECREMENT = **'decrement'**
# 8.4.4. redux/actions_creators.js
*/\* action creator**模块** \*/ * **import** {INCREMENT, DECREMENT} **from** **'./action-types' ** **export const** *increment* = number => ({**type**: INCREMENT, number}) **export const** *decrement* = number => ({**type**: DECREMENT, number})
# 8.4.5. redux/reducers.js
*/\* * *根据老的**state**和指定**action,* *处理返回一个新的**state \*/ * **import** {INCREMENT, DECREMENT} **from** **'../constants/ActionTypes' ** **import** {INCREMENT, DECREMENT} **from** **'./action-types' ** **export function** *counter*(state = 0, action) { **console**.log(**'counter'**, state, action) **switch** (action.**type**) { **case** INCREMENT: **return** state + action.**number ** **case** DECREMENT: **return** state - action.**number ** **default**: **return** state } }
# 8.4.6. components/app.jsx
*/\* * *应用组件** \*/ * **import** React, {Component} **from** **'react' ** **import** PropTypes **from** **'prop-types' ** **import** * **as** actions **from** **'../redux/actions' ** **export default class** App **extends** Component { **static** *propTypes* = { **store**: PropTypes.*object*.isRequired, } *increment* = () => { **const** number = **this**.**refs**.numSelect.**value** * 1 **this**.**props**.**store**.dispatch(actions.*increment*(number)) } *decrement* = () => { **const** number = **this**.**refs**.numSelect.**value** * 1 **this**.**props**.**store**.dispatch(actions.*decrement*(number)) } *incrementIfOdd* = () => { **const** number = **this**.**refs**.numSelect.**value** * 1 **let** count = **this**.**props**.**store**.getState() **if** (count % 2 === 1) { **this**.**props**.**store**.dispatch(actions.*increment*(number)) } } *incrementAsync* = () => { **const** number = **this**.**refs**.numSelect.**value** * 1 setTimeout(() => { **this**.**props**.**store**.dispatch(actions.*increment*(number)) }, 1000) } render() { **return** ( <**div**> <**p**> click {**this**.**props**.**store**.getState()} times {**' '**} </**p**> <**select** **ref****="numSelect"**> <**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*}>increment if odd</**button**> {**' '**} <**button** **onClick****=**{**this**.*incrementAsync*}>increment async</**button**> </**div**> ) } }``
# 8.4.7. store.js
**import** React **from** **'react' ** **import** ReactDOM **from** **'react-dom' ** **import** {createStore} **from** **'redux' ** **import** App **from** **'./components/app' ** **import** {*counter*} **from** **'./redux/reducers' ** *//* *根据**counter**函数创建**store**对象 * **const** store = createStore(*counter*) *//* *定义渲染根组件标签的函数 * **const** *render* = () => { ReactDOM.render( <**App** **store****=**{store}/>, **document**.getElementById(**'root'**) ) } *//* *初始化渲染 * *render*() *//* *注册**(**订阅**)**监听**,* *一旦状态发生改变**,* *自动重新渲染 * store.subscribe(*render*)
# 8.4.8. 问题
redux与react组件的代码耦合度太高
编码不够简洁
# 8.5. react-redux
# 8.5.1. 理解
一个react插件库
专门用来简化react应用中使用redux
# 8.5.2. React-Redux将所有组件分成两大类
UI组件
a. 只负责 UI 的呈现,不带有任何业务逻辑
b. 通过props接收数据(一般数据和函数)
c. 不使用任何 Redux 的 API
d. 一般保存在components文件夹下
容器组件
a. 负责管理数据和业务逻辑,不负责UI的呈现
b. 使用 Redux 的 API
c. 一般保存在containers文件夹下
# 8.5.3. 相关API
Provider
让所有组件都可以得到state数据
connect()
用于包装 UI 组件生成容器组件
import { connect } from 'react-redux' connect( mapStateToprops, mapDispatchToProps )(Counter)
mapStateToprops()
将外部的数据(即state对象)转换为UI组件的标签属性 const mapStateToprops = function (state) { return { value: state } }
mapDispatchToProps()
将分发action的函数转换为UI组件的标签属性
简洁语法可以直接指定为actions对象或包含多个action方法的对象
# 8.5.4. 使用react-redux
下载依赖包
npm install --save react-redux
redux/action_types.js
不变
redux/actions_creators.js
不变
redux/reducers.js
不变
components/counter.jsx
*/\* UI**组件**:* *不包含任何**redux API \*/ * **import** React **from** **'react' ** **import** PropTypes **from** **'prop-types' ** **export default class** Counter **extends** React.Component { **static** *propTypes* = { **count**: PropTypes.*number*.isRequired, **increment**: PropTypes.*func*.isRequired, **decrement**: PropTypes.*func*.isRequired } *increment* = () => { **const** number = **this**.**refs**.numSelect.**value** * 1 **this**.**props**.**increment**(number) } *decrement* = () => { **const** number = **this**.**refs**.numSelect.**value** * 1 **this**.**props**.**decrement**(number) } *incrementIfOdd* = () => { **const** number = **this**.**refs**.numSelect.**value** * 1 **let** count = **this**.**props**.**count ** **if** (count % 2 === 1) { **this**.**props**.**increment**(number) } } *incrementAsync* = () => { **const** number = **this**.**refs**.numSelect.**value** * 1 setTimeout(() => { **this**.**props**.**increment**(number) }, 1000) } render() { **return** ( <**div**> <**p**> click {**this**.**props**.**count**} times {**' '**} </**p**> <**select** **ref****="numSelect"**> <**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*}>increment if odd</**button**> {**' '**} <**button** **onClick****=**{**this**.*incrementAsync*}>increment async</**button**> </**div**> ) } }
containters/App.jsx
*/\* * *包含**Counter**组件的容器组件** \*/ * **import** React **from** **'react' ** *//* *引入连接函数 * **import** {connect} **from** **'react-redux' ** *//* *引入**action**函数 * **import** {*increment*, *decrement*} **from** **'../redux/actions' ** **import** Counter **from** **'../components/counter' ** *//* *向外暴露连接**App**组件的包装组件 * **export default** connect( state => ({**count**: state}), {*increment*, *decrement*} )(Counter)
index.js
**import** React **from** **'react' ** **import** ReactDOM **from** **'react-dom' ** **import** {createStore} **from** **'redux' ** **import** {Provider} **from** **'react-redux' ** **import** App **from** **'./****container****s/app' ** **import** {*counter*} **from** **'./redux/reducers' ** *//* *根据**counter**函数创建**store**对象 * **const** store = createStore(*counter*) *//* *定义渲染根组件标签的函数 * ReactDOM.render( ( <**Provider** **store****=**{store}> <**App** /> </**Provider**> ), **document**.getElementById(**'root'**)``
# 8.5.5. 问题
redux默认是不能进行异步处理的,
应用中又需要在redux中执行异步任务(ajax, 定时器)
# 8.6. redux异步编程
# 8.6.1. 下载redux插件(异步中间件)
npm install --save redux-thunk
# 8.6.2. store.js
**import** {createStore, *applyMiddleware*} **from** **'redux' ** **import** thunk **from 'redux-thunk'**** ** *//* *根据**reducer**函数创建**store**对象 * **const** store = createStore( *reducer*, *applyMiddleware(thunk)* *//* *应用上异步中间件 * )
# 8.6.3. redux/actions.js
*//* *异步**action creator(**返回一个函数**) * **export const** *incrementAsync* = number => { **return** dispatch => { setTimeout(() => { dispatch(*increment*(number)) }, 1000) } }
# 8.6.4. components/counter.jsx
*incrementAsync* = () => { **const** number = **this**.**refs**.numSelect.**value***1 **this**.**props**.**incrementAsync**(number) }
# 8.6.5. containers/app.jsx
**import** {*increment*, *decrement*, *incrementAsync*} **from** **'../redux/actions'**
// *向外暴露连接App组件的包装组件 * export default connect( state => ({count: state}), {increment, decrement, incrementAsync} )(Counter)
# 8.7. 使用上redux调试工具
# 8.7.1. 安装chrome浏览器插件
# 8.7.2. 下载工具依赖包
npm install --save-dev redux-devtools-extension
# 8.7.3. 编码
Store.js
**import** { **composeWithDevTools** } **from** **'redux-devtools-extension'**``* * **const** store = createStore( *reducer*, **composeWithDevTools**(*applyMiddleware*(thunk)) * * ) |
---|
|
# 8.8. 相关重要知识: 纯函数和高阶函数
# 8.8.1. 纯函数
一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
必须遵守以下一些约束
a. 不得改写参数数据
b. 不会产生任何副作用,例如网络请求,输入和输出设备
c. 不能调用Date.now()或者Math.random()等不纯的方法
- redux的reducer函数必须是一个纯函数
# 8.8.2. 高阶函数
理解: 一类特别的函数
a. 情况1: 参数是函数
b. 情况2: 返回是函数
常见的高阶函数:
a. 定时器设置函数
b. 数组的forEach()/map()/filter()/reduce()/find()
c. 函数对象的bind()
d. Promise() / then()
e. antd中的Form.create()()
f. react-router-dom中的withRouter
g. react-redux中的connect()
作用:
a. 能实现更加动态, 更加可扩展的功能