携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情
第1章:React入门
1.1. React的基本认识
1.1.1. 官网
- 英文官网: https://reactjs.org/
- 中文官网: doc.react-china.org/
1.1.2. 介绍描述
- 于构建用户界面的 JavaScript 库(只关注于View)
- 由Facebook开源
1.1.3. React的特点
- Declarative(声明式编码)
- Component-Based(组件化编码)
- Learn Once, Write Anywhere(支持客户端与服务器渲染)
- 高效
- 单向数据流
1.1.4. React高效的原因
- 虚拟(virtual)DOM, 不总是直接操作DOM
- DOM Diff算法, 最小化页面重绘
1.2. React的基本使用
注意: 此时只是测试语法使用, 并不是真实项目开发使用
1.2.1. 效果
1.2.2. 相关js库
- react.js: React的核心库
- react-dom.js: 提供操作DOM的react扩展库
- babel.min.js: 解析JSX语法代码转为纯JS语法代码的库
1.2.3. 在页面中导入js
<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>
1.2.4. 编码
<script type="text/babel"> //必须声明babel
// 创建虚拟DOM元素
const vDom = <h1>Hello React</h1> // 千万不要加引号
// 渲染虚拟DOM到页面真实DOM容器中
ReactDOM.render(vDom, document.getElementById('test'))
</script>
1.2.5. 使用React开发者工具调试
1.3. React JSX
1.3.1. 效果
1.3.2. 虚拟DOM
- React提供了一些API来创建一种
特别
的一般js对象
var element = React.createElement('h1', {id:'myTitle'},'hello')
- 上面创建的就是一个简单的虚拟DOM对象
- 虚拟DOM对象最终都会被React转换为真实的DOM
- 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界面
1.3.3. JSX
- 全称: JavaScript XML
- react定义的一种类似于XML的JS扩展语法: XML+JS
- 作用: 用来创建react虚拟DOM(元素)对象
-
var ele = Hello JSX!
-
注意1: 它不是字符串, 也不是HTML/XML标签
-
注意2: 它最终产生的就是一个JS对象
- 标签名任意: HTML标签或其它标签
- 标签属性任意: HTML标签属性或其它
- 基本语法规则
- 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析
- 遇到以 { 开头的代码,以JS语法解析: 标签中的js代码必须用{ }包含
- babel.js的作用
- 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
- 只要用了JSX,都要加上type="text/babel", 声明需要babel来处理
1.3.4. 渲染虚拟DOM(元素)
- 语法: ReactDOM.render(virtualDOM, containerDOM)
- 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示
- 参数说明
- 参数一: 纯js或jsx创建的虚拟dom对象
- 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)
1.3.5. 建虚拟DOM的2种方式
- 纯JS(一般不用)
React.createElement('h1', {id:'myTitle'}, title)
- JSX:
<h1 id='myTitle'>{title}</h1>
1.3.6. JSX练习
需求: 动态展示列表数据
1.4. 模块与组件和模块化与组件化的理解
1.4.1. 模块
- 理解: 向外提供特定功能的js程序, 一般就是一个js文件
- 为什么: js代码更多更复杂
- 作用: 复用js, 简化js的编写, 提高js运行效率
1.4.2. 组件
- 理解: 用来实现特定(局部)功能效果的代码集合(html/css/js)
- 为什么: 一个界面的功能更复杂
- 作用: 复用编码, 简化项目编码, 提高运行效率
1.4.3. 模块化
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
1.4.4. 组件化
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
第2章:React面向组件编程
2.1. 基本理解和使用
2.1.1. 效果
2.1.2. 自定义组件(Component) :
- 定义组件(2种方式)
- 方式1: 工厂函数组件(简单组件)
function MyComponent () {
return <h2>工厂函数组件(简单组件)</h2>
}
- 方式2: ES6类组件(复杂组件)
class MyComponent2 extends React.Component {
render () {
console.log(this) // MyComponent2的实例对象
return <h2>ES6类组件(复杂组件)</h2>
}
}
- 渲染组件标签
ReactDOM.render(<MyComponent />, document.getElementById('example1'))
2.1.3. 注意
- 组件名必须首字母大写
- 虚拟DOM元素只能有一个根元素
- 虚拟DOM元素必须有结束标签
2.1.4. render()渲染组件标签的基本流程
- React内部会创建组件实例对象
- 得到包含的虚拟DOM并解析为真实DOM
- 插入到指定的页面元素内部
2.2. 组件三大属性1: state
2.2.1. 效果
2.2.2. 理解
- state是组件对象最重要的属性, 值是对象(可以包含多个数据)
- 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
2.2.3. 编码操作
- 初始化状态:
constructor (props) {
super(props)
this.state = {
stateProp1 : value1,
stateProp2 : value2
}
}
- 读取某个状态值
this.state.statePropertyName
- 更新状态---->组件界面更新
this.setState({
stateProp1 : value1,
stateProp2 : value2
})
2.3. 组件三大属性2: props
2.3.1. 效果
需求:
- 如果性别没有指定, 默认为男
- 如果年龄没有指定, 默认为18
2.3.2. 理解
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
2.3.3. 作用
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
2.3.4. 编码操作
- 内部读取某个属性值
this.props.propertyName
- 对props中的属性值进行类型限制和必要性限制
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
- 扩展属性: 将对象的所有属性通过props传递
<Person {...person}/>
3. 默认属性值
Person.defaultProps = {
name: 'Mary'
}
- 组件类的构造函数
constructor (props) {
super(props)
console.log(props) // 查看所有属性
}
2.3.5. 面试题
问题: 请区别一下组件的props和state属性
- state: 组件自身内部可变化的数据
- props: 从组件外部向组件内部传递数据, 组件内部只读不修改
2.4. 组件三大属性3: refs与事件处理
2.4.1. 效果
需求: 自定义组件, 功能说明如下:
- 点击按钮,
- 当第2个输入框失去焦点时, 提示这个输入框中的值
2.4.2. 组件的3大属性之二: refs属性
- 组件内的标签都可以定义ref属性来标识自己
<input type="text" ref={input => this.msgInput = input}/>
- 回调函数在组件初始化渲染完或卸载时自动调用
- 在组件中可以通过this.msgInput来得到对应的真实DOM元素
- 作用: 通过ref获取组件内容特定标签对象, 进行读取其相关数据
2.4.3. 事件处理
- 通过onXxx属性指定组件的事件处理函数(注意大小写)
- React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
- 通过event.target得到发生事件的DOM元素对象
<input onFocus={this.handleClick}/>
handleFocus(event) {
event.target //返回input对象
}
2.4.4. 强烈注意
- 组件内置的方法中的this为组件对象
- 在组件类中自定义的方法中this为null
- 强制绑定this: 通过函数对象的bind()
- 箭头函数(ES6模块化编码时才能使用)
2.5. 组件的组合
2.5.1. 效果
功能: 组件化实现此功能
- 显示所有todo列表
- 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本
2.5.2. 功能界面的组件化编码流程(无比重要)
- 拆分组件: 拆分界面,抽取组件
- 实现静态组件: 使用组件实现静态页面效果
- 实现动态组件
- 动态显示初始化数据
- 交互功能(从绑定事件监听开始)
2.6. 收集表单数据
2.6.1. 效果
需求: 自定义包含表单的组件 1. 输入用户名密码后, 点击登陆提示输入信息 2. 不提交表单
2.6.2. 理解
- 问题: 在react应用中, 如何收集表单输入数据
- 包含表单的组件分类
- 受控组件: 表单项输入数据能自动收集成状态
- 非受控组件: 需要时才手动读取表单输入框中的数据
2.7. 组件生命周期
2.7.1. 效果
需求: 自定义组件
- 让指定的文本做显示/隐藏的渐变动画
- 切换持续时间为2S
- 点击按钮从界面中移除组件界面
2.7.2. 理解
- 组件对象从创建到死亡它会经历特定的生命周期阶段
- React组件对象包含一系列的勾子函数(生命周期回调函数), 在生命周期特定时刻回调
- 我们在定义组件时, 可以重写特定的生命周期回调函数, 做特定的工作
2.7.3. 生命周期流程图
2.7.4. 生命周期详述
- 组件的三个生命周期状态:
- Mount:插入真实 DOM
- Update:被重新渲染
- Unmount:被移出真实 DOM
- React 为每个状态都提供了勾子(hook)函数
- componentWillMount()
- componentDidMount()
- componentWillUpdate()
- componentDidUpdate()
- componentWillUnmount()
- 生命周期流程:
- 第一次初始化渲染显示: ReactDOM.render()
- constructor(): 创建对象初始化state
- componentWillMount() : 将要插入回调
- render() : 用于插入虚拟DOM回调
- componentDidMount() : 已经插入回调
- 每次更新state: this.setSate()
- componentWillUpdate() : 将要更新回调
- render() : 更新(重新渲染)
- componentDidUpdate() : 已经更新回调
- 移除组件: ReactDOM.unmountComponentAtNode(containerDom)
- componentWillUnmount() : 组件将要被移除回调
2.7.5. 重要的勾子
- render(): 初始化渲染或更新渲染调用
- componentDidMount(): 开启监听, 发送ajax请求
- componentWillUnmount(): 做一些收尾工作, 如: 清理定时器
- componentWillReceiveProps(): 后面需要时讲
2.8. 虚拟DOM与DOM Diff算法
2.8.1. 效果
class HelloWorld extends React.Component {
constructor(props) {
super(props)
this.state = {
date: new Date()
}
}
componentDidMount () {
setInterval(() => {
this.setState({ >date: new Date()
})
}, 1000)
}
render () {
console.log('render()')
return (
<p>
<input type="text" placeholder="Your name here"/>!  It is {this.state.date.toTimeString()}
</p>
)
}
}
ReactDOM.render(
<HelloWorld/>,
document.getElementById('example')
)
2.8.2. 基本原理图
第3章:react应用(基于react脚手架)
3.1. 使用create-react-app创建react应用
3.1.1. react脚手架
- xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
- 包含了所有需要的配置
- 指定好了所有的依赖
- 可以直接安装/编译/运行一个简单效果
- react提供了一个用于创建react项目的脚手架库: create-react-app
- 项目的整体技术架构为: react + webpack + es6 + eslint
- 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
3.1.2. 创建项目并启动
npm install -g create-react-app
create-react-app hello-react
cd hello-react
npm start
3.1.3. react脚手架项目结构
ReactNews
|-nodemodules---第三方依赖模块文件夹
|-public
----|- index.html-----------------主页面
|-scripts
----|- build.js-------------------build打包引用配置
----|- start.js-------------------start运行引用配置
|-src------------源码文件夹.
|-components-----------------react组件
----|-index.js-------------------应用入口js
|-.gitignore------git版本管制忽略的配置
|-package.json----应用包配置文件
|-README.md-------应用描述说明的readme文件
3.2. demo: 评论管理
3.2.1. 效果
3.2.2. 拆分组件
应用组件: App
- state: comments/array
添加评论组件: CommentAdd
- state: username/string, content/string
- props: add/func
评论列表组件: CommentList
- props: comment/object, delete/func, index/number
评论项组件: CommentItem
- props: comments/array, delete/func
3.2.3. 实现静态组件
3.2.4. 实现动态组件
动态展示初始化数据
- 初始化状态数据.
- 传递属性数据
- 响应用户操作, 更新组件界面.
- 绑定事件监听, 并处理.
- 更新state
第4章:react ajax
4.1. 理解
4.1.1. 前置说明
- React本身只关注于界面, 并不包含发送ajax请求的代码
- 前端应用需要通过ajax请求与后台进行交互(json数据)
- react应用中需要集成第三方ajax库(或自己封装)
4.1.2. 常用的ajax请求库
- jQuery: 比较重, 如果需要另外引入不建议使用
- axios: 轻量级, 建议使用
- 封装XmlHttpRequest对象的ajax
- promise风格
- 可以用在浏览器端和node服务器端
- fetch: 原生函数, 但老版本浏览器不支持
- 不再使用XmlHttpRequest对象提交ajax请求
- 为了兼容低版本的浏览器, 可以引入兼容库fetch.js
4.1.3. 效果
需求:
1. 界面效果如下
2. 根据指定的关键字在github上搜索匹配的最受关注的库
3. 显示库名, 点击链接查看库
4. 测试接口: api.github.com/search/repo…
4.2. axios
4.2.1. 文档
4.2.2. 相关API
- GET请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.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. Fetch
4.3.1. 文档
4.3.2. 相关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)
})
4.4. demo: github users
4.4.1. 效果
4.4.2. 拆分组件
App
state: searchName/string
Search
props: setSearchName/func
List
props: searchName/string
state: firstView/bool, loading/bool, users/array, errMsg/string
4.4.3. 编写静态组件
4.4.4. 编写动态组件
componentWillReceiveProps(nextProps)
:
- 监视接收到新的props, 发送ajax
- 使用axios库发送ajax请求
第5章:几个重要技术总结
5.1. 组件间通信
5.1.1. 方式一: 通过props传递
- 共同的数据放在父组件上, 特有的数据放在自己组件内部(state)
- 通过props可以传递一般数据和函数数据, 只能一层一层传递
- 一般数据-->父组件传递数据给子组件-->子组件读取数据
- 函数数据-->子组件传递数据给父组件-->子组件调用函数
5.1.2. 方式二: 使用消息订阅(subscribe)-发布(publish)机制
- 工具库: PubSubJS
- 下载: npm install pubsub-js --save
- 使用:. import PubSub from 'pubsub-js' //引入. PubSub.subscribe('delete', function(data){ }); //订阅. PubSub.publish('delete', data) //发布消息
5.1.3. 方式三: redux
后面专门讲解
5.2. 事件监听理解
5.2.1. 原生DOM事件
- 绑定事件监听
- 事件名(类型): 只有有限的几个, 不能随便写
- 回调函数
- 触发事件
- 用户操作界面
- 事件名(类型)
- 数据()
5.2.2. 自定义事件(消息机制)
- 绑定事件监听
- 事件名(类型): 任意
- 回调函数: 通过形参接收数据, 在函数体处理事件
- 触发事件(编码)
- 事件名(类型): 与绑定的事件监听的事件名一致
- 数据: 会自动传递给回调函数
5.3. ES6常用新语法
- 定义常量/变量:
const/let
- 解构赋值:
let {a, b} = this.props import {aa} from 'xxx'
- 对象的简洁表达:
{a, b}
- 箭头函数:
- 常用场景
- 组件的自定义方法:
xxx = () => {}
- 参数匿名函数
- 组件的自定义方法:
- 优点: 没有自己的this,使用引用this查找的是外部this
- 扩展(三点)运算符: 拆解对象
(const MyProps = {}, <Xxx {...MyProps}>)
- 类:
class/extends/constructor/super
- ES6模块化:
export default import
第6章:react-router4
6.1. 相关理解
6.1.1. react-router的理解
- react的一个插件库
- 专门用来实现一个SPA应用
- 基于react的项目基本都会用到此库
6.1.2. SPA的理解
- 单页Web应用(single page web application,SPA)
- 整个应用只有一个完整的页面
- 点击页面中的链接不会刷新页面, 本身也不会向服务器发请求
- 当点击路由链接时, 只会做页面的局部更新
- 数据都需要通过ajax请求获取, 并在前端异步展现
6.1.3. 路由的理解
- 什么是路由?
- 一个路由就是一个映射关系
(key:value)
- key为路由路径,
value
可能是function/component
- 路由分类
- 后台路由: node服务器端路由,
value
是function
, 用来处理客户端提交的请求并返回一个响应数据 - 前台路由: 浏览器端路由,
value
是component
, 当请求的是路由path
时, 浏览器端前没有发送http请求, 但界面会更新显示对应的组件
- 后台路由
- 注册路由:
router.get(path, function(req, res))
- 当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
- 前端路由
- 注册路由:
<Route path="/about" component={About}>
- 当浏览器的hash变为#about时, 当前路由组件就会变为About组件
6.1.4. 前端路由的实现
- history库
- 网址: github.com/ReactTraini…
- 管理浏览器会话历史(history)的工具库
- 包装的是原生BOM中window.history和window.location.hash
- history API
History.createBrowserHistory()
: 得到封装window.history的管理对象History.createHashHistory()
: 得到封装window.location.hash的管理对象history.push()
: 添加一个新的历史记录history.replace()
: 用一个新的历史记录替换当前的记录history.goBack()
: 回退到上一个历史记录history.goForword()
: 前进到下一个历史记录history.listen(function(location){})
: 监视历史记录的变化
- 测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>history test</title>
</head>
<body>
<p><input type="text"></p>
<a href="/test1" onclick="return push('/test1')">test1</a><br><br>
<button onClick="push('/test2')">push test2</button><br><br>
<button onClick="back()">回退</button><br><br>
<button onClick="forword()">前进</button><br><br>
<button onClick="replace('/test3')">replace test3</button><br><br>
<script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
<script type="text/javascript">
let history = History.createBrowserHistory() // 方式一
// history = History.createHashHistory() // 方式二
// console.log(history)
function push (to) {
history.push(to)
return false
}
function back() {
history.goBack()
}
function forword() {
history.goForward()
}
function replace (to) {
history.replace(to)
}
history.listen((location) => {
console.log('请求路由路径变化了', location)
})
</script>
</body>
</html>
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 准备
- 下载react-router:
npm install --save react-router@4
- 引入bootstrap.css:
6.3.2 index.js
import React from 'react';
import {render} from 'react-dom';
import { BrowserRouter } from "react-router-dom";
import './index.css';
import App from './App';
render((
<BrowserRouter> //用router管理整个应用
<App />
</BrowserRouter>
), document.getElementById('root'));
6.3.3 路由组件: views/about.jsx
import React from 'react'
export default function About() {
return <div>About组件内容</div>
}
6.3.4. 路由组件: views/home.jsx
import React from 'react'export default function About() {
return <div>Home组件内容</div>}
6.3.5. 包装NavLink组件: components/my-nav-link.jsx
import React from 'react'
import {NavLink} from 'react-router-dom'
export default function MyNavLink(props) {
return <NavLink {...props} activeClassName='activeClass'/>
}
6.3.6. 应用组件: components/app.jsx
import React, { Component } from 'react'
import {Switch, Route, Redirect } from "react-router-dom";
import MyNavLink from './components/my-nav-link';
import About from './views/about';
import Home from './views/home';
export 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">
<MyNavLink activeClassName="activeClass" className="list-group-item" to='/about'>about</MyNavLink>
<MyNavLink activeClassName="activeClass" className="list-group-item" 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 to='/home' />
</Switch>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default App
6.3.7. 自定义样式: index.css
.activeClass {
color: red !important;
}
6.4. 嵌套路由使用
6.4.2. 二级路由组件: views/news.jsx
import React, { Component } from 'react';
export default class News extends Component {
state = {
newArr: ['news001','news002','news003',]
}
render() {
return (
<ul>
{
this.state.newArr.map((news,index) => <li key={index}>{news}</li>)
}
</ul>
);
}
}
6.4.3. 二级路由组件: views/message.jsx
import React, { Component } from 'react';
export default class Message extends Component {
state = {
messages: []
}
componentDidMount = () => {
// 模拟发送ajax请求异步获取数据
setTimeout(() => {
const messages = [{id: 1, title: 'message001'},
{id: 3, title: 'message003'},
{id: 5, title: 'message005'},]
this.setState({messages})
}, 1000);
};
render() {
return (
<ul>
{
this.state.messages.map((m,index)=> (
<li key={index}>
<a href="...">{m.title}</a>
</li>
))
}
</ul>
);
}
}
6.4.4. 一级路由组件: views/home.jsx
import React, { Component } from 'react';
import { Switch, Route, Redirect } from "react-router-dom";
import MyNavLink from '../components/my-nav-link'
import News from './news'
import Message from './message'
export default class Home extends Component {
render() {
return (
<div>
<h2> Home 组件内容 </h2>
<div>
<ul className='nav nav-tabs'>
<li>
<MyNavLink to="/home/news" >News</MyNavLink>
</li>
<li>
<MyNavLink to="/home/message" >Message</MyNavLink>
</li>
</ul>
<div>
<Switch>
<Route path='/home/news' component={News} />
<Route path='/home/message' component={Message} />
<Redirect to='/home/news' />
</Switch>
</div>
</div>
</div>
);
}
}
6.5. 向路由组件传递参数数据
6.5.2. 三级路由组件: views/message-detail.jsx
import React from 'react'
const allMessages = [
{ id: 1, title: 'message001', content: '我爱你1' },
{ id: 3, title: 'message003', content: '我爱你3' },
{ id: 5, title: 'message005', content: '我爱你5' },
]
export default (props) => {
// 得到请求参数中的id
const {id} = props.match.params
// 查询得到对应的message
const message = allMessages.find(item => item.id === parseInt(id))
return (
<ul>
<li>ID: {message.id} </li>
<li>Title: {message.title} </li>
<li>Content: {message.content} </li>
</ul>
)
};
6.5.3. 二级路由组件: views/message.jsx
import React, { Component } from 'react';
import { Route } from "react-router-dom";
import MessageDetail from './message-detail';
import MyNavLink from '../components/my-nav-link';
export default class Message extends Component {
state = {
messages: []
}
componentDidMount = () => {
// 模拟发送ajax请求异步获取数据
setTimeout(() => {
const messages = [{id: 1, title: 'message001'},
{id: 3, title: 'message003'},
{id: 5, title: 'message005'},]
this.setState({messages})
}, 1000);
};
showDetail(id) {
this.props.history.push(`/home/message/messagetail/${id}`)
}
showReplaceDetail(id) {
this.props.history.replace(`/home/message/messagetail/${id}`)
}
render() {
return (
<div>
<ul>
{
this.state.messages.map((m,index)=> (
<li key={index} style={{margin: '10px 0'}}>
<MyNavLink to={`/home/message/messagetail/${m.id}`}>{m.title}</MyNavLink>
<button style={{marginLeft:'10px',}} className="btn" onClick={() => this.showDetail(m.id)}>查看</button>
<button style={{marginLeft:'10px',}} className="btn" onClick={() => this.showReplaceDetail(m.id)}>replace查看</button>
</li>
))
}
</ul>
<Route path='/home/message/messagetail/:id' component={MessageDetail} />
</div>
);
}
}
6.6. 多种路由跳转方式
6.6.2. 二级路由: views/message.jsx
import React, { Component } from 'react';
import { Route } from "react-router-dom";
import MessageDetail from './message-detail';
import MyNavLink from '../components/my-nav-link';
export default class Message extends Component {
state = {
messages: []
}
componentDidMount = () => {
// 模拟发送ajax请求异步获取数据
setTimeout(() => {
const messages = [{id: 1, title: 'message001'},
{id: 3, title: 'message003'},
{id: 5, title: 'message005'},]
this.setState({messages})
}, 1000);
};
showDetail(id) {
this.props.history.push(`/home/message/messagetail/${id}`)
}
showReplaceDetail(id) {
this.props.history.replace(`/home/message/messagetail/${id}`)
}
back = () => {
this.props.history.goBack();
}
forward = () => {
this.props.history.goForward();
}
openPage = () => {
window.location = 'http://www.baidu.com'
}
render() {
return (
<div>
<ul>
{
this.state.messages.map((m,index)=> (
<li key={index} style={{margin: '10px 0'}}>
<MyNavLink to={`/home/message/messagetail/${m.id}`}>{m.title}</MyNavLink>
<button style={{marginLeft:'10px',}} className="btn" onClick={() => this.showDetail(m.id)}>查看</button>
<button style={{marginLeft:'10px',}} className="btn" onClick={() => this.showReplaceDetail(m.id)}>replace查看</button>
</li>
))
}
</ul>
<p>
<button onClick={this.back} className='btn'>回退</button>
<button onClick={this.forward} className='btn'>前进</button>
</p>
<p>
<button onClick={this.openPage} className='btn'>页面跳转</button>
</p>
<Route path='/home/message/messagetail/:id' component={MessageDetail} />
</div>
);
}
}
第7章:react-ui
7.1. 最流行的开源React UI组件库
7.1.1. material-ui(国外)
- 官网: www.material-ui.com/#/
- github: github.com/callemall/m…
7.1.2. ant-design(国内蚂蚁金服)
- PC官网: ant.design/index-cn
- 移动官网: mobile.ant.design/index-cn
- Github: github.com/ant-design/…
- Github: github.com/ant-design/…
7.2. ant-design-mobile使用入门
7.2.2. 使用create-react-app创建react应用.
7.2.3. 搭建antd-mobile的基本开发环境
- 下载
yarn add antd-mobile --save
- src/App.jsx
import React, { Component } from 'react';
import { Button, Toast } from 'antd-mobile';
export default class App extends Component {
handleClick = () => {
Toast.info('This is a toast tips !!!', 1);
}
render() {
return (
<div >
<Button type="primary" onClick={this.handleClick}>Start</Button>
</div>
);
}
}
- src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
//整体引入css
import 'antd-mobile/dist/antd-mobile.css';
ReactDOM.render(<App />, document.getElementById('root'));
- index.html
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
if(!window.Promise) {
document.writeln('<script src="https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js"'+'>'+'<'+'/'+'script>');
}
</script>
7.2.4. 实现按需打包(组件js/css)
- 下载依赖包
yarn add react-app-rewired customize-cra --save-dev
yarn add babel-plugin-import --dev
- 修改默认配置:
package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject"
},
config-overrides.js
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', { libraryName: 'antd-mobile', style: 'css', }),
);
- 编码
// import 'antd-mobile/dist/antd-mobile.css'
// import Button from 'antd-mobile/lib/button'
// import Toast from 'antd-mobile/lib/toast'
import {Button, Toast} from 'antd-mobile'
第8章:redux
8.1. redux理解
8.1.1. 学习文档
- 英文文档: redux.js.org/
- 中文文档: www.redux.org.cn/
- Github: github.com/reactjs/red…
8.1.2. redux是什么?
- redux是一个独立专门用于做状态管理的JS库(不是react插件库)
- 它可以用在react, angular, vue等项目中, 但基本与react配合使用
- 作用: 集中式管理react应用中多个组件共享的状态
8.1.3. redux工作流程
8.1.4. 什么情况下需要使用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个方面的属性
- type: 标识属性, 值为字符串, 唯一, 必要属性
- xxx: 数据属性, 值类型任意, 可选属性
- 例子
const action = {
type: 'INCREMENT'
data: 2 //统一都叫data
}
- 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
}
- 注意
- 返回一个新的状态
- 不要修改原来的状态
8.3.3. store
- 将state,action与reducer联系在一起的对象
- 如何得到此对象
import {createStore} from 'redux
import reducer from './reducers
const store = createStore(reducer)
- 此对象的功能
getState(): 得到stat
dispatch(action): 分发action, 触发reducer调用, 产生新的stat
subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
8.4. 使用redux编写应用
8.4.2. 下载依赖包
yarn add --save redux
8.4.3. redux/action-types.js
// 包含所有action type的常量字符串
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
8.4.4. redux/actions.js
// 包含所有的action creator
import { INCREMENT,DECREMENT } from "./action-types";
export const increment = (number) => ({type: INCREMENT, data: number})
export const decrement = (number) => ({type: DECREMENT, data: number})
8.4.5. redux/reducers.js
/**
* 包含n个reducer函数的模块
*/
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.data
case DECREMENT:
return state - action.data
default:
return state
}
}
8.4.6. components/app.jsx
import React, { Component } from 'react'
import * as actions from "./redux/actions";
export default class App extends Component {
// 增加
increment = () => {
// 1. 得到选择增加数量
const number = this.select.value * 1
// 2. 调用store的方法更新状态
this.props.store.dispatch(actions.increment(number))
}
// 删除
decrement = () => {
const number = this.select.value * 1
this.props.store.dispatch(actions.decrement(number))
}
// 如果是奇数进行加的操作
incrementIfOdd = () => {
const number = this.select.value * 1
// 得到原本的状态
const count = this.props.store.getState()
// 如果是奇数
if(count%2 ===1) {
this.props.store.dispatch(actions.increment(number))
}
}
incrementAsync = () => {
const number = this.select.value * 1
setTimeout(() => {
this.props.store.dispatch(actions.increment(number))
}, 1000);
}
render() {
const count = this.props.store.getState();
// debugger
return (
<div>
<p>click {count} times</p>
<div>
<select ref={select => this.select = select}>
<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>
</div>
)
}
}
8.4.7. index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './redux/store';
function render() {
ReactDOM.render(<App store={store} />, document.getElementById('root'));
}
// 初始化渲染
render()
// 订阅监听(store中的状态变化了,就会自动调用进行重绘)
store.subscribe(function () {
render()
})
8.4.8. 问题
- redux与react组件的代码耦合度太高
- 编码不够简洁
8.5. react-redux
8.5.1. 理解
- 一个react插件库
- 专门用来简化react应用中使用redux
8.5.2. React-Redux将所有组件分成两大类
- UI组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 通过props接收数据(一般数据和函数)
- 不使用任何 Redux 的 API
- 一般保存在components文件夹下
- 容器组件
- 负责管理数据和业务逻辑,不负责UI的呈现
- 使用 Redux 的 API
- 一般保存在containers文件夹下
8.5.3. 相关API
- Provider
- 让所有组件都可以得到state数据
<Provider store={store}>
</Provider>
- 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.js
不变
- redux/reducers.js
不变
- components/counter.jsx
UI组件: 不包含任何redux API
import React, { Component } from 'react'
import PropTypes from 'prop-types';
export default class Counter extends Component {
static propTypes = {
count: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired
}
// 增加
increment = () => {
// 1. 得到选择增加数量
const number = this.select.value * 1
// 2. 调用store的方法更新状态
this.props.increment(number)
}
// 删除
decrement = () => {
const number = this.select.value * 1
this.props.decrement(number)
}
// 如果是奇数进行加的操作
incrementIfOdd = () => {
const number = this.select.value * 1
// 得到原本的状态
// const count = this.props.count
const {count} = this.props
// 如果是奇数
if(count%2 ===1) {
this.props.increment(number)
}
}
incrementAsync = () => {
const number = this.select.value * 1
setTimeout(() => {
this.props.increment(number)
}, 1000);
}
render() {
const {count} = this.props
// debugger
return (
<div>
<p>click {count} times</p>
<div>
<select ref={select => this.select = select}>
<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>
</div>
)
}
}
- containters/app.jsx
import React from 'react'
import { connect } from "react-redux";
import {increment,decrement} from "../redux/actions";
import Counter from '../components/counter';
export default connect(
state => ({count: state}),
{increment,decrement}
)(Counter)
- index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from "react-redux";
import App from './containers/app';
import store from './redux/store';
ReactDOM.render((
<Provider store={store}>
<App />
</Provider>
), document.getElementById('root'));
8.5.5. 问题
1) redux默认是不能进行异步处理的, 2) 应用中又需要在redux中执行异步任务(ajax, 定时器)
8.6. redux异步编程
8.6.1. 下载redux插件(异步中间件)
npm install --save redux-thunk
8.6.2. store.js
// ~redux/store.js
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import { counter } from './reducers';
// 生成一个store对象
const store = createStore( //内部会第一次调用reducer函数得到初始state
counter, // 根据counter函数创建store对象
applyMiddleware(thunk) // 应用上异步中间件
)
export default store
8.6.3. redux/actions.js
// 异步action
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 React from 'react'
import { connect } from "react-redux";
import {increment,decrement,incrementAsync} from "../redux/actions";
import Counter from '../components/counter';
export default connect(
state => ({count: state}),
{increment,decrement,incrementAsync}
)(Counter)
8.7. 使用上redux调试工具
8.7.1 chrome安装调试工具
8.7.2. 下载工具依赖包
yarn add redux-devtools-extension --save-dev
8.7.3. 编码
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension'
import { counter } from './reducers';
// 生成一个store对象
const store = createStore( //内部会第一次调用reducer函数得到初始state
counter, // 根据counter函数创建store对象
composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
)
export default store