第1章 课程导学
学习流程相关的知识点
学习前提
要有一些js、es6、webpack、npm等基础知识
第2章 React初探
2-1 React简介
React
FaceBook 推出
2013年开源
函数式编程
使用人数最多的前端框架之一
健全的文档与完善的社区
2-2 React开发环境准备
1)、确定自己电脑已经安装了node环境
根据自己电脑来安装属于自己电脑版本的node,推荐使用稳定版
node官网下载地址
2)、安装react脚手架create-react-app
npx create-react-app my-app
cd my-app
npm start
2-3 工程目录文件简介
因为通过脚手架下载的程序,有很多文件是需要我们剔除出去的。
例如没用的样式文件、测试文件、pwa相关的一些文件等等
-
项目结构
- ├── node_modules # 第三方的依赖库文件
- ├── public
- │ ├── favicon.ico # Favicon
- │ ├── index.html # 首页
- ├── src
- │ ├── app.js # 业务代码文件
- │ ├── index.js # 应用入口
- ├── README.md
- └── .gitignore #git忽略提交的文件配置
- └── package.json #启动、编译命令设置;依赖包管理配置信息
- └── yarn.lock #yarn 依赖包管理配置信息
2-4 React中的组件
下面就是定义了一个App的组件
//App.js
import React, {Component} from 'react'
class App extends Component {
render() {
//JSX 语法
return (
<div>hello world!</div>
)
}
}
export default App
App组件引用和调用
//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from 'App.js'
ReactDOM.render(<App />,document.getElementById('root'))
JSX语法在使用的时候,组件的定义和使用的时候要大写
JSX语法中,我们要求一个组件render函数返回的内容,外层必须要有一个大的元素包裹(div或者Fragment)
Fragment渲染到页面的时候是不占位的
eg:
//index.js
import App from 'App.js' //大写
ReactDOM.render(<App />,document.getElementById('root'))
第3章 React基础精讲
3-1 使用React编写TodoList功能
//TodoItem.js
import React, {Component} from 'react';
import PropTypes from 'prop-types'; //组件类型进行校验
class TodoItem extends Component {
constructor(props) {
super(props);
this.handleDelete = this.handleDelete.bind(this)
}
render() {
const {content, test} = this.props;
return (
<div onClick={this.handleDelete}>{test}-{content}</div>
)
}
handleDelete() {
const {index, deleteItem} = this.props;
deleteItem(index)
}
}
TodoItem.propTypes = {
test: PropTypes.string.isRequired, //父组件没有给子组件传递test 但是又是必填项,所以可以通过defaultProps给一个默认值
content: PropTypes.string,
deleteItem: PropTypes.func,
index: PropTypes.number
};
TodoItem.defaultProps = {
test: 'hello'
};
export default TodoItem
//TodoList.js
import React, {Component, Fragment} from 'react';
import TodoItem from './TodoItem';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
list: []
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleAdd = this.handleAdd.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
render() {
return (
<Fragment>
<div>
<label htmlFor="inputArea">输入内容:</label>
<input
id="inputArea"
type="text"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<button onClick={this.handleAdd}>提交</button>
</div>
<ul>
{this.getTodoItem()}
</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return (
<TodoItem
content={item}
key={index}
index={index}
deleteItem={this.handleDelete}
/>
)
})
}
handleInputChange = (e) => {
const value = e.target.value;
this.setState(() => {
return {
inputValue: value
}
})
};
handleAdd = () => {
this.setState((prevState) => {
return {
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}
});
};
handleDelete = (index) => {
this.setState((prevState) => {
const list = [...prevState.list];
list.splice(index, 1);
return {list}
});
}
}
export default TodoList;
3-2 JSX语法细节补充
1.注释:必须要用花括号括起来
单行注释
{
//这里是单行注释
}
多行注释
{/*这是多行注释*/}
2.样式名称引入:将class改为className
因为class这个和定义组件“类”有冲突
<input className="inputClass" />
3.表单label for属性的引用,要将for改为htmlFor
防止for和js中循环for引起冲突
<label htmlFor="inputArea">用户名</label>
<input id="inputArea" />
4.将input标签内容直接进行转义呈现在页面 dangerouslySetInnerHTML
<li
key={index}
onClick={this.handleDeleteItem.bind(this, index)}
dangerouslySetInnerHTML={{__html: item}}
>
</li>
3-3 组件拆分与组件之间的传值
1.组件拆分
当一个页面很大的时候,那么我们就会对这个页面进行组件的拆分,拆分的结构就像一棵树一样
组件之间的结构
2.组件之间的传值
父组件向子组件传值是通过“子组件的属性”
子组件接受父组件传过来的值:this.props.属性名称
子组件向父组件传值是通过 子组件调用父组件的方法,从来改变父组件的数据,但是要对这个方法调用的时候,父组件要对这个方法进行this的绑定
3-4 TodoList 代码优化
//解构优化
this.props.content ,this.props.index
改为
const {content,index} = this.props
调用的时候,直接使用content,index即可
//this的绑定优化 都可以写在constructor里面
onChange={this.handleChange.bind(this)}
constructor(props){
super(props);
....
....
this.handleChange = this.handleChange.bind(this) //优化地方
}
调用的时候直接写:
this.handleChange
//Ui优化
当一段的UI太过于庞大的是时候,可以把这段UI放到一个函数方法里面,return 返回出去,同时在使用的地方调用一下即可
//setState键值对优化为异步函数
this.setState({
inputValue: e.target.value
})
改为
this.setState(()=>{
const value = e.target.value //在异步里面必须要提出来,如果直接须赋值会报错的
return {
inputValue:value //如果将value 改为e.target.value 会报错
}
})
//setState中this.state 改为 参数prevState
handleAdd = () => {
this.setState((prevState) => {
return {
list: [...prevState.list, prevState.inputValue],
inputValue: ''
}
});
};
//if else判断改变为 switch case也可以提高性能
3-5 围绕 React 衍生出的思考
声明式开发
可以与以其他框架并存
组件化
单项数据流(父组件可以向子组件传值,但是子组件一定不能直接去改变这个值)
视图层框架 (负责视图渲染,但是针对一些大数据和复杂数据的时候,需要交由redux等数据框架来处理)
函数式编程 (便于自动化测试)
第4章 React高级内容
4-1 React developer tools 安装及使用
打开chrome浏览器,并且打开chrome应用商店,搜索React Developer Tools 添加即可
React Developer Tools 地址
百度网盘下载地址
4-2 PropTypes 与 DefaultProps 的应用
创建好的组件,最好给组件定义属性类型(string、number、boolean等等) 和创建默认值。就拿上面todoList这个例子来说吧。
PropTypes
父组件(TodoList)向子组件(TodoItem)传递了content(字符串)、index(数字)、deleteItem(方法),那么如果我们在子组件中声明了这些类型,就可以避免一些不别要的麻烦,如果父组件传递过来的属性值不是我们想要的,那么我就可以告诉浏览器我需要什么类型的值
DefaultProps
可以当父组件没有给子组件传递任何值的时候,通过DefaultProps给子组件初始化一些数据(即默认值)
详细代码见 3-1 使用React编写TodoList功能
了解更多PropTypes知识链接地址
4-3 props,state 与 render 函数的关系
1.当组件的state或者props发生改变时,render函数就会重新执行
2.当父组件的render函数被重新执行时,它的子组件的render函数都将被重新执行
eg:
在render函数的后面进行打印
打印父组件console.log('parent-render')
打印子组件console.log('child1-render')
更改父组件的state值
你会发现console里面 parent-render 和 child1-render 都被打印出来了
4-4 React 中的虚拟DOM
1.state 数据
2.JSX 模板
3.数据+模板 结合,生成真实DOM,来显示
4.state 发生改变
5.数据+模板 结合,生成真实DOM,替换原始DOM
缺陷:
第一次生成一个完整的DOM片段
第二次生成一个完整的DOM片段
第二次的DOM替换第一次的DOM,非常消耗性能
解决??
1.state 数据
2.JSX 模板
3.数据+模板 结合,生成真实DOM,来显示
4.state 发生改变
5.数据+模板 结合,生成真实DOM,并不直接替换原始DOM
6.新的DOM(documentFragment)和原始的DOM,做对比,找差异
7.找出input框发生改变的部分
8.只用新的DOM中的input元素,替换老的DOM中input的元素
缺陷:
性能的提升并不明显
接着解决:
1.state 数据
2.JSX 模板
3.生成虚拟DOM(其实就是一个js对象,用它来描述真实DOM)(损耗了性能但是极小)
['div',{id:'abc'},['span',{},'hello world']]
4.用虚拟DOM的结构生成真实DOM,来显示
<div id="abc"><span>hello world</span></div>
5.state 发生改变
6.数据+模板 结合,生成新的虚拟DOM (极大的提升了性能)
['div',{id:'abc'},['span',{},'bye bye']]
7.比较原始虚拟DOM和生成新的虚拟DOM,找到区别是span的内容(极大的提升了性能)
8.直接操作DOM,改变span的内容
总结如下:
虚拟DOM其实就是js对象,那为什么采用虚拟dom会提高性能呢?因为他们比对的是js对象,而不是真的DOM,从而极大的提升了性能
4-5 深入了解虚拟DOM
JSX(模板) => 虚拟DOM(js对象) =>真实的DOM
render() {
return <div>item</div>
}
等价于
render() {
return React.createElement('div',{},'item')
}
虚拟DOM有点:
1.性能提升了
2.它是的跨度应用得以实现 React Native
因为数据+模板生成的是虚拟DOM,
1)、如果是在网页中,那么再有虚拟DOM(JS对象)生成真实的DOM,浏览器可以得以识别,所以网页中可以得到应用
2)、如果在App中,那么再用虚拟DOM(JS对象)不去生成真实的DOM,而去生成原生的组件,那么在App中也就可以得到应用
4-6 虚拟 DOM 中的 Diff 算法
虚拟DOM什么时候回发生比对呢?
答:那就是当state值发生改变的时候,虚拟DOM才会开始进行比对
为什么setState设计成为异步的?
答:我们知道,当state发生改变或者props(即父组件中的state发生改变)时,也就是当我们调用setState方法的时候,state值发生改变,虚拟DOM开始进行比较。
那么如果连续3次调用setState方法的时候,变更3组数据(我们此时会想react会进行三次虚拟DOM比对,三次渲染页面),其实react会把短时间内连续调用setState方法合并为一个setState,只去做一次虚拟DOM的比对,然后更新一次DOM,这样就可以省去额外两次DOM比对带来的性能消耗,所以把setState设计成为异步的
react中虚拟DOM比对采用的是diff算法:其实同层比对,key值比对
同层比对 key值比对
所以列表循环中不要用index作为key值的,在diff算法进行比较时候,会导致key变更,而产生一些性能上的问题
因为index(索引值有时候会变更)会导致key值不稳定,
eg:
a 0 b 1 c 2
当删除a
b 0 c 1
使用一个稳定的值作为key才是我们首先要考虑的
eg:
a a b b c c
当输出a
b b c c
4-7 React 中 ref 的使用
ref是帮助我们react直接获得dom元素,一般情况下我们尽量不要使用ref
eg:
<input
id="inputArea"
type="text"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input) => this.input = input} //ref 使用
/>
handleInputChange = (e) => {
//const value = e.target.value;
const value = this.input.value; //通过ref 来获取input的value值
this.setState(() => {
return {
inputValue: value
}
})
};
tip:
当我们在试用ref获取dom元素的时候,有时候会出现数据不对,或者少一步操作现象,那么因为setState是异步函数,解决这个问题就是把ref相关操作的内容放到setState回调函数中进行操作
<ul ref={(ul)=>this.ul = ul}>
<div>hello</div>
</ul>
this.setState(() => {
return {
inputValue: value
}
},() => {
//回调成功后,在这进行ref相关操作 this.ul.querySelectorAll('div').length
})
在React v16.3 版本中引入的 React.createRef()
方法
//使用方法
import React, {Component} from 'react';
class Products extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef(); //这个地方
}
handleChangeInputValue = () => {
console.log(this.myRef.current.value) //取值的时候借助current属性
};
render() {
return (
<div>
<input type="text" ref={this.myRef} onChange={this.handleChangeInputValue}/>
</div>
)
}
}
export default Products
Ref的适合使用的几个场景:
- 处理焦点、文本选择或媒体控制。
this.textInput.current.focus();
- 触发强制动画。
- 集成第三方 DOM 库
4-8 React 的生命周期函数
生命周期函数:
某一时刻自动调用执行的函数。
//虽然不是react的生命周期函数,但是有这个功能,初始化数据就放在constructor里面进行操作的
1.初始化
constructor(props){
super(props);
}
2.挂载的生命周期函数(Mounting)
//当组件即将要被挂载到页面的时候,会被执行
componentWillMount(){}
//render
render(){}
//当组件被挂载到页面后,会被执行
componentDidMount(){}
3.数据发生改变更新的生命周期函数(Updating)
//当组件被更新之前,会被执行
shouldComponentUpdate(nextProps,nextState){
const {list} = this.props;
if(nextProps.list !== list){
return true
}else{
return false
}
}
//当组件被更新之前,会被执行,但是在shouldComponentUpdate后面
//如果shouldComponentUpdate 返回true 它才执行
//如果返回 false 它不会被执行
componentWillUpdate(){}
//当组件被更新之后,会被执行
componentDidUpdate(){}
//一个组件要从父组件接受参数
//如果这个组件第一次存在于父组件中,不会被执行
//如果这个组件之前已经存在父组件中,才会被执行
componentWillReceiveProps(nextProps){}
4.组件从页面卸载的生命周期函数
//当这个组件将要被从页面剔除出去的时候,会被执行
componentWillUnmount(){}
react生命周期函数
4-9 React 生命周期函数的使用场景
1.生命周期使用场景
//针对组件的优化的时候,可以使用shouldComponentUpdate,当组件当中的属性值发生改变的是去做更新,没有改变则不去更新,减少组件被重复多次渲染的频率,减少性能的消耗
shouldComponentUpdate(nextProps, nextState) {
const {content} = this.props;
if (nextProps.content !== content) {
return true
} else {
return false
}
}
//ajax接口请求 放在componentDidMount()里面
import axios from 'axios'
componentDidMount(){
axios.get('api')
.then((res)=>{
console.log('success')
})
.catch(()=>{
console.log('error')
})
}
2.性能优化
1)、shouldComponentUpdate
2)、setState采用的是异步回调的方式,提高了性能
3)、虚拟DOM中的同层比对和key值比对,也极大的提高了性能
4-10 使用Charles实现本地数据mock
charles 下载安装
//桌面创建一个todoList.json文件
["react","vue","angular"]
//设置charles
打开 charles ---> 菜单栏tools ---> Map Local Setting
//请求接口
componentDidMount() {
axios.get('/api/todoList')
.then((res) => {
this.setState(() => {
return {
list: [...res.data]
}
})
})
.catch(() => {
console.log('error')
})
}
Map Local Setting设置
4-11 React 中实现 CSS 过渡动画
css过度动画主要还是css3知识点的理解和认识,比如:transition(all 1s ease-in)、animation(show-item 1s ease-in forwards)、@keyframes等等
还就就是借助第三方库react-transition-group等等
//state
constructor(props) {
super(props);
this.state = {
show: true
};
this.handleToggle = this.handleToggle.bind(this);
}
//JSX
<h3>动画部分</h3>
<div className={this.state.show ? 'show' : 'hide'}>hello</div>
<button onClick={this.handleToggle}>toggle</button>
//绑定事件
handleToggle() {
this.setState((prevState) => {
return {
show: prevState.show === true ? false : true
}
})
}
//style
.show {
/*opacity: 1;*/
/*transition: all 1s ease-in;*/
animation: show-item 2s ease-in forwards;
}
.hide {
/*opacity: 0;*/
/*transition: all 1s ease-in;*/
animation: hide-item 2s ease-in forwards;
}
@keyframes show-item {
0% {
opacity: 0;
color: red;
}
50% {
opacity: 0.5;
color: green;
}
100% {
opacity: 1;
color: blue;
}
}
@keyframes hide-item {
0% {
opacity: 1;
color: red;
}
50% {
opacity: 0.5;
color: green;
}
100% {
opacity: 0;
color: blue;
}
}
4-12 使用 react-transition-group 实现动画
import { CSSTransition } from 'react-transition-group';
<CSSTransition
in={this.state.show}
timeout={1000}
classNames="fade"
unmountOnExit
onEntered={(el)=>{el.style.color='blue'}}
appear={true}
>
<div>hello</div>
</CSSTransition>
//css
/*CSSTransition*/
.fade-enter,fade-appear{
opacity: 0;
}
.fade-enter-active,fade-appear-active{
opacity: 1;
transition: opacity 1s ease-in;
}
.fade-enter-done{
opacity: 1;
}
.fade-exit{
opacity: 1;
}
.fade-exit-active{
opacity: 1;
transition: opacity 1s ease-in;
}
.fade-exit-done{
opacity: 0;
}
当我们想一个数组都拥有动画的时候,我们会用react-transition-group里面的TransitionGroup
了解更多链接地址
5-1 Redux 入门
-1 Redux 概念简述
react是一个视图层框架
那么,redux就是一个数据层框架
Redux = Reducer + Flux
image.png
5-2 Redux 的工作流程
Redux 的工作流程React Component:借书者
Action Creators: 我要借本三国演义这句话
Store: 图书馆管理员
Reducer:记录本
React Component:我想要借本三国演义
Action Creators:那我给你写封信(dispatch 发号了一个action指令),我把这个指令告诉了Store
Store:我知道了,但是我不知道这本书放到哪了,我帮你问问Reducers
Reducer:我给你查查,查到了,在这呢。带着三国演义这本书(newState)给了Store,Store记录下了借书的内容信息,并这本书最终给了React Components
5-3 使用 Antd 实现 TodoList 页面布局
//1.安装antd并且引入antd样式文件
npm install antd -S
//2.todoList.js
import React, {Component} from 'react';
import {Input, Button, List} from 'antd';
import 'antd/dist/antd.css';
import store from './store'
class TodoList extends Component {
constructor(props) {
super(props);
this.handleInputValue = this.handleInputValue.bind(this);
this.state = store.getState();
}
render() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<Input
value={this.state.inputValue}
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
/>
<Button type="primary" >提交</Button>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.state.list}
renderItem={item => (<List.Item>{item}</List.Item>)}
></List>
</div>
)
}
handleInputValue(e) {
const action = {
type: 'change_input_value',
value: e.target.value
};
store.dispatch(action)
}
}
export default TodoList;
5-4 创建 redux 中的 store
//在src目录下创建store目录,并在store目录下创建index.js(图书馆管理员)和reducer.js(记录本)
//index.js
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store
//reducer.js
const defaultState = {
inputValue: '123',
list: [1, 2]
};
export default (state = defaultState, action) => {
return state
}
//TodoList.js
import store from './store'
constructor(props) {
super(props);
this.state = store.getState();
console.log(store.getState()) //打印出来的数据是{inputValue: "123",Array(2)}
}
创建图书馆store 并打通 store和reducer之间的桥梁,
然后react component 通过 store.getState()方法拿到 reducer里面的数据了
5-5 Action 和 Reducer 的编写
1.Redux DevTools安装和配置
去chrome应用商店 安装 Redux DevTools这个浏览器插件
Redux DevTools下载链接地址
安装完了以后,发现用chrome浏览器打开控制台,点击redux标签,并没有显示任何内容,那是因为需要我们在store文件中写一段代码。
配置相关信息的地址,打开github,搜索 redux-devtools-extension,就能查看相关的配置信息了
import {createStore} from 'redux';
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() //这段代码,为了方便浏览器能捕获到redux数据
);
export default store
2.redux的工作流程原理演示(todoList这个例子来说)
根据上面的redux工作流,咱们来说。
如果我们想要更改React Component(input里面的值)
那么我们需要创建一个Action creators,并且定义一个action对象,通过store.dispatch方法传递给store
handleInputValue(e) {
//定义一个action
const action = {
type: 'change_input_value',
value: e.target.value
};
// 通过store.dispatch方法,把action传递给store
store.dispatch(action)
}
当store接受到Action creators发送过来的action的时候,它说我需要我的秘书reducer帮我查询一下,并且帮我处理一下。
于是乎reducer就就收了两个参数,第一个是state(定义初始化的旧数据),第二个就是传递过来的action(一个type名称,一个是传递过来新的inputValue值),
reducer说:我不能直接去更改state里面的值,我需要把state值通过JSON的parse和stringify进行数据深层拷贝生成newState。那么在对这个newState进行数据的处理,最后把处理好的数据再return 回去
store拿到 reducer处理好的新数据后,
再通过自己的store.getState()方法,去拿到reducer的最新数据
再通过自己的store.subscribe()方法,去监测store里面的数据变化,
最后通过this.setState()方法,把最新的数据渲染到页面上去
通过第一方法专门是获得最新数据的store.subscribe(this.handleStore);
//reducer.js
const defaultState = {
inputValue: '123',
list: [1, 2]
};
export default (state = defaultState, action) => {
console.log(state, action);
if (action.type === 'change_input_value') {
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState
}
if (action.type === 'add_list_item') {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
return newState
}
return state
}
//TodoList.js
import React, {Component} from 'react';
import {Input, Button, List} from 'antd';
import 'antd/dist/antd.css';
import store from './store'
class TodoList extends Component {
constructor(props) {
super(props);
this.handleInputValue = this.handleInputValue.bind(this);
this.handleAdd = this.handleAdd.bind(this);
this.handleStore = this.handleStore.bind(this);
this.state = store.getState();
store.subscribe(this.handleStore);//检测handleStore方面里面数据的变化
}
render() {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<Input
value={this.state.inputValue}
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
onChange={this.handleInputValue}
/>
<Button type="primary" onClick={this.handleAdd}>提交</Button>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.state.list}
renderItem={item => (<List.Item>{item}</List.Item>)}
></List>
</div>
)
}
handleInputValue(e) {
//定义一个action 把这个type:干什么事情的名字传递过去,并且发生更改的信息传递过去
const action = {
type: 'change_input_value',
value: e.target.value
};
store.dispatch(action)
}
handleAdd() {
const action = {
type: 'add_list_item'
};
store.dispatch(action)
}
handleStore() {
//把变更后新的数据,重新放入到state中,然后去渲染页面
this.setState(store.getState());
}
}
export default TodoList;
5-6 ActionTypes 的拆分
为什么要把action里面的type 拆分到一个文件里面呢?
第一当我们把type值拼写错误的时候,不好找错
第二我们需要调用相同的内容,写两次
所以我们在store文件下面创建了一个actionType.js
5-7 使用 actionCreator 统一创建 action
为什么要把把组件中的action通过方法的形式拆分出去呢?
第一为了方便action的统一管理
第二为了减少React Component 代码的繁琐
//在store文件下创建actionCreators.js
import {CHANGE_INPUT_VALUE, ADD_LIST_ITEM, DELETE_LIST_ITEM} from './actionTypes';
export const getChangeInputValue = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getAddListItem = () => ({
type: ADD_LIST_ITEM
});
export const getDeleteListItem = (index) => ({
type: DELETE_LIST_ITEM,
index
});
//TodoList.js
import {getChangeInputValue, getAddListItem, getDeleteListItem} from './store/actionCreators';
handleInputValue(e) {
const action = getChangeInputValue(e.target.value);
store.dispatch(action)
}
handleAdd() {
const action = getAddListItem();
store.dispatch(action)
}
handleDeleteItem(index) {
const action = getDeleteListItem(index);
store.dispatch(action);
}
5-8 Redux 知识点复习补充
1.Redux在设计和使用的三个原则
1).store是唯一的
2).只有store能改变自己的内容
3).reducer必须是个纯函数(给个固定的输入,就一定会有固定的输出,且不会有任何副作用)
所以里面不能有异步操作(ajax),不能有时间的操作new Date()
2.redux中核心的API
1).createStore 帮助我们创建一个store
2).store.dispatch() 帮助我们派发一个action
3).store.getState() 帮助我们获得store当中所有的数据
1).store.subscribe() 帮助我们订阅(监测)store当中数据的改变
第6章 Redux进阶
6-1 UI组件和容器组件
1.UI组件
UI组件负责页面的渲染
eg:
import React, {Component} from 'react'
import {Button, Input,List} from "antd";
class TodoListUi extends Component{
render(){
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<Input
value={this.props.inputValue}
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
onChange={this.props.handleInputValue}
/>
<Button type="primary" onClick={this.props.handleAdd}>提交</Button>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={this.props.list}
renderItem={(item, index) => (
<List.Item onClick={() => this.props.handleDeleteItem(index)}>{item}</List.Item>)}
></List>
</div>
)
}
}
export default TodoListUi;
2.容器组件
它不管你的UI长成什么样子,它只负责页面的业务逻辑
import React, {Component} from 'react';
import TodoListUi from './TodoListUi';
import 'antd/dist/antd.css';
import store from './store'
import {getChangeInputValue, getAddListItem, getDeleteListItem} from './store/actionCreators';
class TodoList extends Component {
constructor(props) {
super(props);
this.handleInputValue = this.handleInputValue.bind(this);
this.handleAdd = this.handleAdd.bind(this);
this.handleDeleteItem = this.handleDeleteItem.bind(this);
this.handleStore = this.handleStore.bind(this);
this.state = store.getState();
store.subscribe(this.handleStore);
}
render() {
return (
<TodoListUi
inputValue={this.state.inputValue}
list={this.state.list}
handleInputValue={this.handleInputValue}
handleAdd={this.handleAdd}
handleDeleteItem={this.handleDeleteItem}
/>
)
}
handleInputValue(e) {
const action = getChangeInputValue(e.target.value);
store.dispatch(action)
}
handleAdd() {
const action = getAddListItem();
store.dispatch(action)
}
handleDeleteItem(index) {
const action = getDeleteListItem(index);
store.dispatch(action);
}
handleStore() {
this.setState(store.getState());
}
}
export default TodoList;
6-2 无状态组件
当一个组件只有render函数的时候,那么就可以把这个组件改写为无状态组件
优势:性能比较高
无状态组件是一个函数
而一般组件是声明的一个类,这个类里面还有一些生命周期函数,所以执行起来不仅需=要执行类还要执行render
那么什么时候去用无状态组件呢?
当我们定义UI组件的时候,因为没有业务逻辑,只有一个render,所以一般在情况下,在UI组件中我们使用无状态组件比较多一些
//无状态组件
import React from 'react'
import {Button, Input, List} from "antd";
const TodoListUi = (props) => {
return (
<div style={{marginTop: '10px', marginLeft: '10px'}}>
<Input
value={props.inputValue}
placeholder="todo info"
style={{width: '300px', marginRight: '10px'}}
onChange={props.handleInputValue}
/>
<Button type="primary" onClick={props.handleAdd}>提交</Button>
<List
style={{marginTop: '10px', width: '300px'}}
bordered
dataSource={props.list}
renderItem={(item, index) => (
<List.Item onClick={() => props.handleDeleteItem(index)}>{item}</List.Item>)}
></List>
</div>
)
};
export default TodoListUi;
6-3 Redux 中发送异步请求获取数据
//在actionTypes.js中创建一个变量
export const INIT_LIST_DATA = 'init_list_data';
//在actionCreators.js中创建一个action
export const getTodoListData = (data) => ({
type: INIT_LIST_DATA,
data
});
/TodoList.js
import axios from 'axios';
import {getTodoListData} from './store/actionCreators';
componentDidMount() {
axios.get('/api/todoList')
.then((res) => {
const data = res.data;
const action = getTodoListData(data);
store.dispatch(action)
})
}
//reducer.js
import {INIT_LIST_DATA} from './actionTypes.js'
const defaultState = {
inputValue:'',
list:[]
}
export default (state = defaultState, action) =>{
if(action.type === INIT_LIST_DATA){
const newState = JSON.parse(JSON.stringify(state))
newState.list = action.data
return newState
}
return state
}
6-4 使用Redux-thunk 中间件实现ajax数据请求
1.Redux-thunk 安装 以及 redux-Devtools的配置
//安装
npm install redux-thunk -S
//redux-Devtools的配置 store文件下的index.js
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
export default store
配置参考地址链接:redux-devtools-extension
2.redux-thunk 在程序中的应用
为什么使用redux-thunk这个redux中间件?
第一:可以把数据操作和数据请求操作从React Component中搬到ActionCreators.js里面,不会是的组件显得那么拥堵
第二:便于后期的单元测试
//actionCreators.js中的修改
import axios from 'axios';
export const getTodoListDataAction = (data) => ({
type: INIT_LIST_DATA,
data
});
export const getListData = () => {
//redux-thunk 返回action是一个函数的时候,且放到了action里面进行操作的
return (dispatch) => {
axios.get('/api/todoList')
.then((res) => {
const data = res.data;
const action = getTodoListDataAction(data);
dispatch(action)
})
}
};
//TodoList.js中的修改
import { getListData } from './store/actionCreators';
componentDidMount() {
const action = getListData();
store.dispatch(action);
}
6-5 什么是Redux的中间件
redux数据工作流redux-thunk 其实是对store.dispatch(action)方法的一个封装和升级,是把异步请求的操作放到了action当中进行操作。
在没有使用redux-thunk的时候,定义的action是一个对象
使用redux-thunk之后,定义的action不仅可以是对象,而且还可以可以是一个函数
其他redux中间件:
redux-logger:可以记录action每次派发的日志
redux-saga:也是解决react中异步的一个中间件,单独的把异步的操作放到一个文件中进行操作
6-8 Redux-saga中间件入门
1.redux-sage的安装和配置
//安装
npm install redux-saga -S
//配置是在 store文件下面的index.js中
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
// import thunk from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
import todoSaga from './sagas'
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(sagaMiddleware),
);
const store = createStore(reducer, enhancer);
sagaMiddleware.run(todoSaga);
export default store
//sagas.js
2.redux-saga在程序中的应用
//TodoList.js
componentDidMount() {
const action = getTodoListData();
store.dispatch(action);
}
//actionTypes.js
export const INIT_LIST_DATA = 'init_list_data';
//actionCreators.js
import {INIT_LIST_DATA} from './actionTypes';
export const getTodoListData = (data) => ({
type: INIT_LIST_DATA,
data
});
//reducer.js
import {INIT_LIST_DATA} from './actionTypes';
const defaultState = {
inputValue: '',
list: []
};
export default (state = defaultState, action) => {
// console.log(state, action);
if (action.type === INIT_LIST_DATA) {
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.data;
return newState
}
return state
}
//sagas.js
import {takeEvery, put} from 'redux-saga/effects';
import {INIT_LIST_DATA} from './actionTypes';
import {getTodoListData} from './actionCreators';
import axios from 'axios';
function* getListData() {
try {
const res = yield axios.get('/api/todoList');
const action = getTodoListData(res.data);
yield put(action)
} catch(e) {
console.log('todoList 网络异常')
}
}
function* todoSaga() {
yield takeEvery(INIT_LIST_DATA, getListData);
}
export default todoSaga;
总结:
1).ajax请求
不采用Promise那种形式了(.then),而是通过yield 来等待返回的结果
2).接受或者监听Action的
通过的takeEvery,检测到变量名称,触发一个generator函数
3).派发请求
不采用store.dispatch(), 而是通过的是put()
4).出了takeEvery、put方法还有(takeLatest、call等等多种API)
6-9 如何使用 React-redux
react-redux核心API有哪些?
1).Provider:就是一个连接器的组件,因为Provider和store做了关联,所以Provider这些内部的组件都可以获取到store里面的数据内容了
//安装react-redux
npm install react-redux -S
//使用 在src文件下面的index.js文件进行编写
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import TodoList from './TodoList'
import store from './store'
const App = (
<Provider store={store}>
<TodoList />
</Provider>
)
ReactDOM.render(App,document.getElementById('root'))
2).connect: 是React Component调用react-redux的connect方法,使得组件和store关联起来,并且能对state进行设置和修改
import React,{ Component } from 'react'
import {connect} from 'react-redux;
class TodoList extends Component {
render() {
return (
<div>
<div>
<input
value={this.props.inputValue}
onChange={this.props.handleInputChange}
/>
<button>提交</button>
</div>
<div>
<ul><li>hello</li></ul>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue
}
}
//store.dispatch,props
mapDispatchToProps = (dispatch) => {
return {
handleInputChange(e) {
const action = {
type:'change_input_value',
value: e.target.value
}
dispatch(action)
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(TodoList)
6-12 最终TodoList功能
通过react官网提供的脚手架工具(creat-react-app)来搭建项目
1).采用了react全家桶:
react
react-dom
react-redux
redux
redux-thunk
2).ajax请求
axios
3).项目目录
项目目录
4).代码展示
//src文件下的 index.js
import React from 'react'
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import TodoList from './TodoList'
import store from './store'
const App = (
<Provider store={store}>
<TodoList/>
</Provider>
)
ReactDOM.render(App, document.getElementById('root'));
//TodoList.js
import React, {Component} from 'react'
import {connect} from "react-redux";
import {getInputValueAction, getHandleClickAction, getDeleteItemAction, getListDataApi} from './store/actionCreators'
class TodoList extends Component {
render() {
const {inputValue, list, handleInputChange, handleClick, handleDelete} = this.props;
return (
<div>
<div>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
/>
<button onClick={handleClick}>提交</button>
</div>
<div>
<ul>
{
list.map((item, index) => (
<li key={index} onClick={() => handleDelete(index)}>{item}</li>
))
}
</ul>
</div>
</div>
)
}
componentDidMount() {
this.props.getListData()
}
}
const mapStateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
};
const mapDispatchToProps = (dispatch) => {
return {
handleInputChange(e) {
const action = getInputValueAction(e.target.value);
dispatch(action)
},
handleClick() {
const action = getHandleClickAction();
dispatch(action)
},
handleDelete(index) {
const action = getDeleteItemAction(index);
dispatch(action)
},
getListData() {
const action = getListDataApi();
dispatch(action);
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
//store 文件下的index.js
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));
const store = createStore(reducer, enhancer);
export default store;
//store 文件下的reducer.js
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, GET_LIST_DATA} from './actionTypes';
const defaultState = {
inputValue: '',
list: []
};
export default (state = defaultState, action) => {
if (action.type === CHANGE_INPUT_VALUE) {
const newState = JSON.parse(JSON.stringify(state));
newState.inputValue = action.value;
return newState
}
if (action.type === ADD_ITEM) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.push(newState.inputValue);
newState.inputValue = '';
return newState
}
if (action.type === DELETE_ITEM) {
const newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.index, 1);
return newState
}
if (action.type === GET_LIST_DATA) {
const newState = JSON.parse(JSON.stringify(state));
newState.list = action.data;
return newState
}
return state
}
//store 文件下的actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_ITEM = 'add_item';
export const DELETE_ITEM = 'delete_item';
export const GET_LIST_DATA = 'get_list_data';
//store 文件下的actionCreators.js
import axios from 'axios';
import {CHANGE_INPUT_VALUE, ADD_ITEM, DELETE_ITEM, GET_LIST_DATA} from './actionTypes';
export const getInputValueAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
});
export const getHandleClickAction = () => ({
type: ADD_ITEM
});
export const getDeleteItemAction = (index) => ({
type: DELETE_ITEM,
index
});
export const getListDataAction = (data) => ({
type: GET_LIST_DATA,
data
});
export const getListDataApi = () => {
return (dispatch) => {
axios.get('/api/todoList')
.then(res => {
const data = res.data;
const action = getListDataAction(data);
dispatch(action)
})
.catch((e) => {
console.log('/api/todoList 网络异常')
})
}
};
第7章 项目实战中的一些技巧
7-1 styled-components的应用
在写react组件的时候,为了防止样式被污染到,我们可以通过styled-components自定义标签以及样式。
//1.安装 styled-components
npm install styled-components -S
//2.初步使用方法,创建一个style.js文件
import styled from 'styled-components';
export const Nav = styled.div`
width:1000px;
margin: 0 auto;
height: 50px;
line-height: 50px;
&.txtColor{
color:red
}
`
组件中引用
import {Nav} from './style.js'
<Nav className="txtColor">
//3.attrs属性
export const NavItem = styled.a.attrs({
href: '/'
})`
//样式
`
export const NavItem = styled.input.attrs({
placeholder: '搜索'
})`
//样式
`
//4.嵌套的应用
import { Nav,NavItem} from './style.js'
<Nav>
<NavItem className="bg">首页</NavItem>
</Nav>
export const Nav = styled.div`
width:1000px;
margin: 0 auto;
height: 50px;
line-height: 50px;
&.txtColor{
color:red
}
//嵌套写法
.bg{
background: red
}
`
//5.全局样式的使用(createGlobalStyle),比如reset.css、iconfont.css等等
export const GlobalStyle = createGlobalStyle`
//reset.css内容 或者 iconfont.css 内容等等
`;
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import Main from './Main';
import store from './store';
import {GlobalStyle} from './style';
import {GlobalStyle2} from './statics/font/iconfont'
const App = (
<Provider store={store}>
<GlobalStyle/> //这个地方就可以设置为全局样式了
<GlobalStyle2/>//这个地方就可以设置为全局样式了
<Main/>
</Provider>
);
ReactDOM.render(App, document.getElementById('root'));
//6.参数传递和获取
应用场景,当我们都在循环一个列表的数据的时候,需要传递这个img作为它的背景图片
<Toppic imgUrl = '......png'></Topic>
<Toppic imgUrl = '......png'></Topic>
import styled from 'styled-components'
export const Topic = styled.div`
background:url(${(props)=>props.imgUrl})
`;
上面是styled-components的一些常用的使用方法,要是想学习和了解更多。
styled-components更多学习和了解地址
7-2 redux中 combinReducers 的使用
在开发过程中,我们不可能把所有的reducer放到一个文件里面,那么肯定需要对reducer进行拆分的,但是拆分后的reducer最后我们肯定需要在合并到一起呢,因为在redux在创建store的时候,需要reducer的集合作为入参的。所有合并reducer就诞生了combinReducers
import { combinReducers } from 'reducer'
import {reducer as headerReducer} from '../common/header/store'
import {reducer as footerReducer} from '../common/footer/store'
...
const reducer = combinReducers({
header: headerReducer,
footer: footerReducer
...
})
export default reducer
ps: 调用的时候注意了
const mapState = (state) => {
return {
focused: state.header.focused //调用时候,你加上你header或者footer
}
};
7-3 store的拆分
store的拆分上面我们写TodoList的demo的是,因为只涉及两个页面,所以不会考虑到store的拆分,但是在我们制作项目的时候,我们就的考虑store的拆分了
1.最外层的store文件(store总中心):(index.js 和 reducer.js)
index.js:创建store,并且把store和reducer链接起来,而且配置了redux-devtools可以让我们在chrome里面看到redux的变化
reducer.js: 把项目中各个地方的reducer通过combinReducers方便合并起来,把合并的最终结果reducer,暴露出去
//index.js
import {createStore, applyMiddleware, compose} from "redux";
import reducer from './reducer';
import thunk from 'redux-thunk';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
export default store;
//reducer.js
import {combineReducers} from "redux";
import {reducer as headerReducer} from '../common/header/store'
const reducer = combineReducers({
header: headerReducer
});
export default reducer;
2.组件中的store文件(碎片store,那公共header为例)的拆分:
index.js: 把下面三个文件整合到一起,并且暴露出去,为了更方便的引用
constants.js: 定义了一些大写的常量,且暴露出去这些常量
actionCreators.js: header组件中action的一些方法都放在这个里面,且暴露出去
reducer.js: header组件中的reducer操作放在这个里面,且暴露出去
//index.js:
import reducer from './reducer';
import * as constants from './constants';
import * as actionCreators from './actionCreators';
export {
reducer, //为了总store中reducer更方便的引入
constants,//为了actionCreator更方便的引入
actionCreators//为了组件中更方便的引入
}
//总store中reducer引入的时候:import {reducer as headerReducer} from '../common/header/store'
//actionCreator引入的时候:import {constants} from './index';
//header组件引入的时候:import {actionCreators} from './store';
//constants.js
export const INPUT_FOCUSED = 'header/input_focused';
export const INPUT_BLUR = 'header/input_blur';
//actionCreators.js
import {constants} from './index';
export const getInputFocusedAction = () => ({
type: constants.INPUT_FOCUSED
});
export const getInputBlurAction = () => ({
type: constants.INPUT_BLUR
});
7-4 使用 Immutable.js 来管理store中的数据
为了确保原始state不会被修改,导致的一些问题。所以我们引入了Immutable.js来更好维护我们的原始state数据
//1.安装 immutable
npm install immutable -S
//2.使用 immutable中的fromJS 可以把 js对象转变为 immutable对象
import {constants} from './index';
import { fromJS } from 'immutable';
const defaultState = fromJS({
focused:false
})
//3.设置 更改state里面的 immutable数据那么就需要.set()方法
//immutable对象的set方法,会结合之前immutable对象的值和设置的值,返回一个全新的对象
export default (state = defaultState,action) =>{
if(action.type === constants.INPUT_FOCUSED) {
return state.set('focused',true)
}
if(action.type === constants.GET_HEADER_LIST) {
//return state.set('list', ).set('totalPage', );
//改为 state.merge()方法的
return state.merge({
list: action.data,
totalPage:action.totalPage
});
}
return state
}
//4.获取 要使用immutable数据 要通过.get方法
const mapState = (state) =>{
return {
focused: state.header.get('focused')
}
}
//5.获取 当需要把immutable对象转化为 普通的js对象时候
const {list} = this.props
const newList = list.toJS() //toJS方法的使用
7-5 使用 redux-immutable 统一数据格式
上一小节说到,我们将state初始化的数据通过immutable这个库变成了immutable对象,确保了state里面数据的稳定性,但是呢,在我们组件去获得immutable的时候:
focused: state.header.get('focused')中
state.header是“js对象”
而后面的.get('focused')则是“immutable对象”
这样看的有些不统一,那么如何把state.header也变成immutable对象呢?那么我们就去看那个地方设置state.header
//安装redux-immutable
npm install redux-immutable -S
//在最外层的reducer.js 文件对跟reducer进行设置
将
import {combineReducers} from "redux";
改为
import {combineReducers} from "redux-immutable";//redux 改为 redux-immutable
import {reducer as headerReducer} from '../common/header/store'
const reducer = combineReducers({
header: headerReducer
});
export default reducer;
//优化代码
const mapState = (state) => {
return {
focused: state.get(header).get('focused')
}
};
改为 连续调用两次get方法通过getIn方法一次实现
const mapState = (state) => {
return {
focused: state.getIn(['header','focused'])
}
};
7-6 避免无意义的请求发送,提升组件性能
有些数据并不是我们每次点击就去请求接口,需要我们初次点击的时候,请求一次接口,随后点击就不请求了,那么就要加一些判断限制一下
const {list} = this.props
//当去请求一个列表的时候,如果初始的数据为0,那么我去请求一次接口
(list.length === 0) && dispatch(action)
ps:这种只是项目中的一种情况,我们在开发过程中呢,要结合项目开发功能,来写不同的判断来减少或者没必要的接口请求
7-7 什么是路由,如何在React中使用路由功能
我们使用的是react-router-dom来做路由的
//安装 react-router-dom
npm install react-router-dom -S
//使用
import React, {Component} from 'react';
import {Provider} from 'react-redux';
import {BrowserRouter, Route} from 'react-router-dom';
import store from './store';
import {GlobalStyle} from './style';
import {GlobalStyle2} from './statics/font/iconfont'
import Header from './common/header';
import Home from './pages/Home';
import Detail from './pages/Detail';
class App extends Component {
render() {
return (
<Provider store={store}>
<GlobalStyle/>
<GlobalStyle2/>
<Header/>
<BrowserRouter>
<div>
<Route path='/' exact component={Home}></Route>
<Route path='/detail' exact component={Detail}></Route>
</div>
</BrowserRouter>
</Provider>
)
}
}
export default App;
2.单页路由的跳转,通过Link 的to属性进行页面的跳转
单页跳转的好处,减少http请求,请求速度会很快
import {Link} from 'react-router-dom';
<Link to='/'>
<Logo />
</Link>
3.页面路由参数的传递
1)、动态路由获取参数
http://localhost:3000/detail/1
//配置路由
<Route path='/detail/:id' exact component={Detail}></Route>
//获取Url上面的1这个参数值
this.props.match.params.id
2)、非动态路由获取参数
http://localhost:3000/detail?id=1
//配置路由
<Route path='/detail' exact component={Detail}></Route>
//获取Url上面的1这个参数值
const id = this.props.location.search; //search: ?id=2
再对id进行数据的处理才能拿到最终我们想要的值
7-8 PureComponent 的应用
import React,{Component} from 'react';
改为
import React,{PureComponent} from 'react';
为了考虑react的性能优化,需要我们对变更的数据的进行监测,采用shouldComponentUpdate对组件进行优化,如果这样的话,就需要写很多shouldComponentUpdate的代码,那么react中这个PureComponent组件 就是自动帮助我们做这部分优化功能的。
注释:如果采用PureComponent这个组件那么,必须保证你的数据是immutable的
网友评论