一 : 脚手架
react官方提供的脚手架工具:Create-react-app
优点:
- 官方提供,比较健壮;
- 使用简单,可定制性强;
- 调试代码方便;
如果没有深厚webpack配置功底,能够确保驾驭更复杂的脚手架工具的话,使用这个脚手架是一个最佳选择
脚手架的代码不能直接运行,需要脚手架工具编译才可以在浏览器运行,grunt/webpack等工具来帮助写脚手架
Create React App:
npm install -g create-react-app
create-react-app my-app
cd my-app
npm start
生成文件:
my-app
- README.md 项目说明文件,支持markdown语法
- package.json 任何一个脚手架工具,都会有,代表node包文件
- .gitinore 使用git管理代码的时候,一些文件不想传到git仓库上,路径写下来
- node_modules 这个项目一些第三方依赖的包,脚手架使用时需要的一些第三方依赖
- public
- favicon.ico 网页标题icon
- index.html
- manifest.json 把网页当成APP使用,保存快捷图标时的配置
- src 项目源代码
- index.js 入口
- App.test.js 自动化测试文件
package.json
{
"name": "my-app", //项目名称
"version": "0.1.0", //版本号
"private": true, //私有项目
"dependencies": { //安装的第三方依赖
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-scripts": "2.1.8"
},
"scripts": { //提供指令调用 npm run start启动
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
serviceWorker的作用:
PWA: progressive web application
通过写网页的形式,来写手机app应用,引入serviceWorker,借助网页来写手机APP应用的功能
效果:写了一个网页,并上传到支持https协议的服务器上,这个网页会具备这样的特性(当用户第一次访问这个网页,需要联网才能看到,下次断网的情况下,依然可以看到上次访问过的页面)serviceWorker会把之前浏览过的页面,存储在浏览器缓存中。
src/index.js
import * as serviceWorker from './serviceWorker'; 引用
serviceWorker.unregister(); 调用
把网页当成APP显示时的配置
手机/电脑 通过快捷方式,直接进入网址
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [ 快捷方式图标
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".", 跳转网址
"display": "standalone",
"theme_color": "#000000", 主题颜色
"background_color": "#ffffff"
}
二:组件
src/index.js
import App from './App';
加载一个App的文件,这个App 就是一个小的组件
App来自App.js文件(src/App.js)
---------------------
src/App.js
import React, { Component } from 'react';
定义一个类App,继承了React.Component的类
当一个类继承了React.Component的类时候,它就是一个组件了
class App extends Component {
render() {
return (
<div>
hello world!
</div>
);
}
render函数返回什么,这个组件就展示什么内容
}
export default App; 导出
{ Component } 等价于 React.Component
import { Component } from 'react';
等价于
import React from 'react';
const Component = React.Component
--------------------
src/index.js
import App from './App'; 引入组件
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM.render() 在做什么:
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
ReactDOM 第三方模块
ReactDOM有一个方法render,可以把一个组件挂载到一个dom节点上
<App /> 是jsx语法,如果使用了jsx语法,
就一定要在项目中引入import React from 'react'语法
组件名首字母必须大写
import app from './App';
ReactDOM.render(<app />, document.getElementById('root'));
这种写法会报错,jsx不支持
在JSX语法中,如果要使用自己创建的组件,组件开头必须用大写字母开头
三:最基础的JSX语法
src/App.js
import React, { Component } from 'react';
class App extends Component {
render() {
// JSX
return (
<div>
hello, 阿鲁提尔
</div>
);
}
}
export default App;
在react中,在js写html标签,就是JSX语法
<App /> 自定义组件
<div> 小写开头,h5原始标签
四:使用React编写Todolist功能
src/TodoList.js
import React, { Component,Fragment } from 'react';
class Todolist extends Component {
//在react中定义数据
// 在js中,一个类就一定会有一个constructor构造函数
// 当创建一个Todolist实例,constructor这个函数是优于其他任何函数
// 最先被执行的函数
// 会接受一个props参数
constructor(props){
super(props)
//Todolist 继承了React.Component组件,所以在创建Todolist时,super是调用了Component的构造函数
//调用一次
//定义数据
//数据定义在状态里面,this.state就是Todolist的状态
//state负责存储组件里面的数据
this.state = {
inputValue:'',
list:['学习英文','学习React']
}
}
render() {
return (
<Fragment>
<div>
<input
value={this.state.inputValue}
onChange={this.handleInputChange.bind(this)}
/>
<button onClick={this.handleBtnClick.bind(this)}>提交</button>
</div>
<ul>
{
this.state.list.map((item,index)=>{
return (
<li
onClick={this.handleItemDelete.bind(this,index)}
key={index}
>
{item}
</li>
)
})
}
</ul>
</Fragment>
)
}
handleInputChange(e){
//改变数据,向里面传入对象
this.setState({
inputValue: e.target.value
})
// this.state.inputValue = e.target.value
}
handleBtnClick(){
this.setState({
list:[...this.state.list,this.state.inputValue],
inputValue:''
})
}
handleItemDelete(index){
const list = [...this.state.list]
list.splice(index,1)
this.setState({
list,
})
错误写法,删除出bug,留下做个标记,一会慢慢研究
// let list = this.state.list.splice(index,1)
// this.setState({
// list,
// })
强烈不推荐错误写法,违反书写规定
// this.state.list.splice(index,1)
// this.setState({
// list:this.state.list
// })
//immutable
//state 不允许我们做任何的改变 所以赋值出来再修改
//一旦修改state里面的内容,后面会影响React性能优化
}
}
export default Todolist;
//React规定,最外层必须包裹一个元素
// 如果想最外层包裹一个元素,这个元素又不被显示出来
//在react16版本里面,提供了一个Fragment占位符
五:JSX语法细节补充
0. JSX中的注释:
{/* do something*/}
1. <input /> 中value和defaultValue的区别:
- 如果input 表单只是显示默认值,不会手动去修改该值,则值需要使用defaultValue
- 如果需要获取用户输入的值,则一定要用value属性,但是一定要配合onchange事件一起使用,否则值是不会变的
2. 样式className代替class(与类冲突)
<input className="input"
value={this.state.InputValue}
onChange={this.handleInputValue.bind(this)}
/>
3. dangerouslySetInnerHTML的作用:
在上面的TodoList中,input输入<h1>学习node.js</h1>
,<h1>也会被当成文本显示,dangerouslySetInnerHTML的作用就是,不显示<h1>标签,当做html添加到页面
dangerouslySetInnerHTML 危险的设置元素innerHTML属性
<li
key={index}
onClick={this.handleRemoveInputValue.bind(this,index)}
dangerouslySetInnerHTML = {{__html:item}}
>
{/* {item} 使用了dangerouslySetInnerHTML = {{__html:item}},这里item就不需要写了 */}
</li>
//dangerouslySetInnerHTML = {{__html:item}}
4. for和htmlFor的区别
<label for="insertArea">输入内容</label>
<input
id="insertArea"
className="input"
value={this.state.InputValue}
onChange={this.handleInputValue.bind(this)}
onKeyDown={this.handleEnterInput.bind(this)}
/>
增加输入框选择区域的功能,虽然可以正常显示,但是会出现如下报错
这是因为这个for会和循环的for产生歧义。要换成htmlFor
六:组件的拆分与组件之间的传值
父组件向子组件传值
src/TodoList.js
import TodoItem from './TodoItem';
<ul>
{
this.state.list.map((item,index)=>{
return (
<div>
<TodoItem content={item} /> //使用组件,通过属性传值
{/*<li //注释内容
key={index}
onClick={this.handleRemoveInputValue.bind(this,index)}
dangerouslySetInnerHTML = {{__html:item}}
>
</li>*/}
</div>
)
})
}
</ul>
src/TodoItem.js
import React,{Component} from 'react';
class TodoItem extends Component{
render(){
return <div>{this.props.content}</div>
{/*子组件通过this.props.content 获取值*/}
}
}
export default TodoItem;
子组件向父组件传值
src/TodoList.js
import React,{Component,Fragment} from 'react';
import './style.css'
import TodoItem from './TodoItem';
class TodoList extends Component{
constructor(props){
super(props)
this.state={
InputValue:'',
list:['学习英语','学习React']
}
}
render(){
return (
<Fragment>
<div>
<label htmlFor="insertArea">输入内容</label>
<input
id="insertArea"
className="input"
value={this.state.InputValue}
onChange={this.handleInputValue.bind(this)}
onKeyDown={this.handleEnterInput.bind(this)}
/>
<button onClick={this.handleUpInputValue.bind(this)}>提交</button>
</div>
<ul>
{
this.state.list.map((item,index)=>{
return (
<div>
<TodoItem
index={index}
content={item}
deleteItem = {this.handleRemoveInputValue.bind(this)}
{/*把函数传给子组件,并把this指向TodoList
把index和item通过属性传递给TodoItem
在TodoItem组件中,使用
this.props.content
this.props.index
this.props.deleteItem
即可使用*/}
/>
</div>
)
})
}
</ul>
</Fragment>
)
}
handleInputValue(e){
this.setState({
InputValue: e.target.value
})
}
handleUpInputValue(){
this.setState({
list:[...this.state.list,this.state.InputValue],
InputValue:''
})
}
handleEnterInput(e){
if(e.keyCode=="13"&&!this.state.InputValue==''){
this.setState({
list:[...this.state.list,this.state.InputValue],
InputValue:''
})
}else if(e.keyCode=="13"&&this.state.InputValue==''){
console.log("不能为空")
}
}
handleRemoveInputValue(index){
const list = [...this.state.list]
list.splice(index,1)
this.setState({
list,
})
}
}
export default TodoList;
----------------------------------------------------
src/TodoItem.js
import React,{Component} from 'react';
class TodoItem extends Component{
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
render(){
return (
<div
onClick={this.handleClick}>
{this.props.content}
</div>)
}
handleClick(){
this.props.deleteItem(this.props.index)
}
}
export default TodoItem;
总结: 父组件中(src/TodoList.js)
<TodoItem
index={index}
content={item}
deleteItem = {this.handleRemoveInputValue.bind(this)}
/>
把函数传给子组件,并把this指向TodoList
把index和item通过属性传递给TodoItem
在TodoItem组件中,使用
this.props.content
this.props.index
this.props.deleteItem
即可使用
子组件中(src/TodoItem.js)
render(){
return (
<div
onClick={this.handleClick}>
{this.props.content} 绑定item
</div>)
}
handleClick(){
this.props.deleteItem(this.props.index)
调用父级函数,并把父级item传递进去
}
七:TodoList代码优化
src/TodoItem.js
import React, { Component } from 'react';
class TodoItem extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
render() {
const {content} = this.props
return (
<li onClick={this.handleClick}>
{content}
</li>
)
}
handleClick(){
const { deleteItem,index } = this.props;
deleteItem(index)
}
}
export default TodoItem;
src/TodoList.js
import React, { Component,Fragment } from 'react';
import TodoItem from './TodoItem';
import './style.css';
class Todolist extends Component {
constructor(props){
super(props)
this.state = {
inputValue:'',
list:['学习英文','学习React']
}
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">输入内容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>
{
this.getTodoItem()
}
</ul>
</Fragment>
)
}
getTodoItem(){
return this.state.list.map((item,index)=>{
return (
<TodoItem
key={index}
content={item}
index={index}
deleteItem={this.handleItemDelete}
/>
)
})
}
handleInputChange(e){
// 新版支持一个函数
// this.setState(()=>{
// return {
// inputValue: e.target.value
// }
// })
// ----------------
// ES6里面也可以去掉return,直接返回一个对象
// 有一个括号,表示返回里面的对象
// this.setState(()=>({
// inputValue: e.target.value
// })
// )
//这样写报错的原因,setState如果传一个函数,是一个异步的,为了性能上的提升
//异步函数 e.target.value会有一些问题
// ----------------
// 解决方法
const value = e.target.value //在外层保存一下
this.setState(()=>({
inputValue: value
}));
// ----------------
// 最初版
// this.setState({
// inputValue: e.target.value
// })
}
handleBtnClick(){
//prevState 指的是修改数据之前是什么样的
//等价于this.state
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}));
}
handleItemDelete(index){
this.setState((prevState)=>{
const list = [...prevState.list];
list.splice(index,1)
return {list}
});
}
}
export default Todolist;
八:React高级内容
React developer tools 安装及使用插件:
google浏览器扩展程序 安装React Developer Tools
安装完成之后,控制台会增加一个React菜单,可以查看页面结构和数据
PropTypes 与 DefaultProps 的应用:
每个组件都有自己的prop参数,这个参数是从父组件接收的一些属性。
- PropTypes的作用是在接受参数的时候,对参数类型做限制。
- DefaultProps的作用是定义参数的默认值。
src/TodoItem.js
import React, { Component } from 'react';
----------------
引入propTypes
import PropTypes from 'prop-types';
------------------
class TodoItem extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
render() {
const {test,content} = this.props
return (
<li onClick={this.handleClick}>
{test} - {content}
</li>
)
}
handleClick(){
const { deleteItem,index } = this.props;
deleteItem(index)
}
}
--------------------------
使用
//组件名字TodoItem,对它的属性做强校验
TodoItem.propTypes = {
content: PropTypes.string, //content的类型必须是string
//PropTypes是上面引入的PropTypes
deleteItem: PropTypes.func,
index: PropTypes.number,
test: PropTypes.number test是没传的值,不会做检验
如果要求必须传test这个属性可以这样写:
test: PropTypes.string.isRequired
也可以写判断
content: PropTypes.oneOfType([PropTypes.number,PropTypes.string])
数字或者字符串
}
有的时候必须要求传递test,但父组件确实没办法传递,可以给test定义一个默认值,来解决报错问题
TodoItem.defaultProps = {
test: 'hello world'
}
--------------------------
export default TodoItem;
从父级传递来的属性有:
content,deleteItem,index
PropTypes 不会阻止程序的正常运行,但是会在控制台弹出一个报错。
isRequired:表示必须要在父组件里传递,不传递会报警告
PropTypes的类型:
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// Anything that can be rendered: numbers, strings, elements or an array
// (or fragment) containing these types.
optionalNode: PropTypes.node,
// A React element (ie. <MyComponent />).
optionalElement: PropTypes.element,
optionalMessage: PropTypes.instanceOf(Message),
...
Typechecking With PropTypes - 文档
props,state与render函数的关系:
- 当组件的state或者props发生改变的时候,render函数就会重新执行
- 当父组件的render函数被运行时,它的子组件的render都会被将被重新运行一次(可以从两个方面理解,1.自身的props变化,重新运行;2.父组件重新渲染,顺带把包含的子组件一起重新运行。)
React中的 虚拟DOM:
深入了解虚拟DOM:
虚拟DOM中的Diff算法:
React中ref的使用:
在React中使用ref来操作DOM
src/TodoList.js
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">输入内容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input)=>{this.input = input}}
{/*构建一个ref引用,这个引用叫this.input指向对应的input dom节点*/}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>
{
this.getTodoItem()
}
</ul>
</Fragment>
)
}
handleInputChange(e){
console.log(e.target)
//其实就是dom节点 <input id="insertArea" class="input" value="">
// const value = e.target.value
const value = this.input.value //e.target使用this.input来替换
this.setState(()=>({
inputValue: value
}));
}
不推荐使用ref,原因:React中建议数据驱动的方式来编写我们的代码,尽量不要直接操作DOM
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">输入内容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input)=>{this.input = input}}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul ref={(ul)=>this.ul=ul}> {/*绑定ul*/}
{
this.getTodoItem()
}
</ul>
</Fragment>
)
}
handleBtnClick(){ //点击按钮
//prevState 指的是修改数据之前是什么样的
//等价于this.state
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}));
console.log(this.ul.querySelectorAll("li").length);
//当点击添加时,获取的页面数据长度总少一个,因为this.setState是异步的
正确办法,使用this.setState第二个参数,也是一个函数,当第一个函数被执行成功的回调
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}),()=>{
console.log(this.ul.querySelectorAll("li").length);
});
}
九:React中的生命周期函数
生命周期函数指在某一个时刻组件会自动调用执行的函数。
Initialization 是组件初始化
constructor(props) {
super(props);
this.state = {
inputValue:''
}
}
在这里会定义state,接收props
constructor是es6语法中自带的函数,所以不算React的生命周期函数。
但是和React生命周期函数没有太大的区别
Mounting 挂载(组件第一次挂载到页面上)
componentWillMount(){}
在组件即将被挂载到页面的时刻自动执行render(){}
组件渲染componentDidMount(){}
在组件被挂载到页面之后,自动被执行
componentWillMount和componentDidMount只会在组件第一次被放到页面上的时候执行(第一次挂载的时候)
Updation 组件更新的时候
- shouldComponentUpdate(){}
组件被更新之前,他会自动被执行(当组件即将被变更的时候)
shouldComponentUpdate会要求返回一个布尔类型的结果
shouldComponentUpdate(){
return true
}
shouldComponentUpdate根据单次直译:你的组件需要被更新吗?
componentWillUpdate(){}
组件被更新之前,它会自动执行,但是他在shouldComponentUpdate之后执行
如果shouldComponentUpdate返回true它才执行
如果返回false,这个函数就不会被执行了render(){}
渲染组件componentDidUpdate(){}
组件更新完成之后,他会被执行componentWillReceiveProps(){}
一个组件如果是顶层组件,没有props传递进来,是不会执行的
当一个组件从父组件接受了参数
如果这个组件第一次存在于父组件中,不会执行
如果这个组件之前已经存在于父组件中,才会执行
(只要父组件的render函数被重新执行了,子组件的这个生命周期函数就会被执行)
Unmounting 组件从页面去除
- componentWillUnmount(){}
当这个组件即将被从页面中剔除的时候,会被执行
React developer tools插件使用:
对于一个组件,render函数被执行有两种情况:
- 在props和state发生变化的时候子组件会被渲染;
- 父组件render函数执行
input输入内容时,子组件被重复渲染在于第二种情况:这个逻辑没问题,但会带来性能上的损耗
父组件input框内容发生变化,子组件没有必要进行重新渲染,现在的机制会造成子组件进行很多无谓的渲染。
性能优化:
src/TodoItem.js 子组件使用shouldComponentUpdate
shouldComponentUpdate(){
return false;
}
{/*shouldComponentUpdate 我的子组件被渲染一次之后,如果子组件需要被更新,那么强制要求不更新*/}
上面写法不是最优写法
src/TodoItem.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class TodoItem extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
//React提升代码性能一:把this作用域的修改放到constructor里面来做,可以保证整个程序里面作用域的绑定只会执行一次,而且可以避免组件一些无谓的渲染
}
// React提升代码性能二:React底层setState内置了性能机制,是异步的函数,可以把多次数据的改变结合成一次来做,降低虚拟DOM的使用频率
//React提升代码性能三:React底层用了虚拟DOM这个概念,还有同层比对,配值概念,来提升虚拟DOM比对速度,来提升React性能
shouldComponentUpdate(nextProps,nextState){
//React提升代码性能四:当我的组件要被更新的时候,props会被更新成什么
// nextProps 指接下来props会被更新成什么样
// nextState 指接下来state会被更新成什么样
if(nextProps.content !== this.props.content){
//如果接下来变化的content不等于当前props.content,说明组件接收的content值发生了变化
//需要让这个组件重新渲染
return true;
}else{
//没有变化 返回false,组件没有被重新渲染的必要
return false
}
}
render() {
console.log("child render")
const {content} = this.props
return (
<li onClick={this.handleClick}>
{content}
</li>
)
}
handleClick(){
const { deleteItem,index } = this.props;
deleteItem(index)
}
}
TodoItem.propTypes = {
content: PropTypes.string,
deleteItem: PropTypes.func,
index: PropTypes.number,
}
export default TodoItem;
通过shouldComponentUpdate生命周期提升了组件性能,避免一个组件做无谓的render
render函数从新执行就意味着React底层需要对组件生成一份虚拟DOM,和之前虚拟DOM做比对,虽然虚拟DOM的比对比真实DOM比对性能要快得多,但是能省略比对过程当然
可以解决更多的性能,shouldComponentUpdate就是做这件事用的
在React中想发送一个ajax请求:
不能再render函数中发送ajax请求,会造成死循环,render函数会被反复执行,只要在input框内输入内容,render函数就会重新执行,就重新发送一次ajax请求,这样不合理。ajax获取一次数据就行了,render函数会获取很多次ajax
在React中,哪一个生命周期函数只会被执行一次?
componentDidMount(){} 在组建被挂在到页面上的时候,会被执行一次,之后就不会再被重新执行了,把ajax请求放到这里比较合适
思考:componentWillMount(){}也只执行一次,把ajax请求放到这里可不可以?
也是没有任何问题的,但是当我们去写react native,或者用React做服务器端同构也就是更深一点的技术,可能会和以后一些更高端技术产生冲突。为了避免冲突,ajax就放到componentDidMount中,永远都不会有任何问题。
思考:把ajax请求放到constructor中可不可以?
也可以,因为constructor也是只执行一次的函数,推荐把ajax写在componentWillMount中
在React项目中如何发送一个ajax请求?
React没有内置ajax,需要安装一些第三方模块来帮助发送ajax请求。
npm install -g yarn
进入目录
yarn add axios
src/TodoList.js
import React, { Component,Fragment } from 'react';
import TodoItem from './TodoItem';
import axios from 'axios'; //引入axios
import './style.css';
class Todolist extends Component {
constructor(props){
super(props)
this.state = {
inputValue:'',
list:[]
}
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.handleItemDelete = this.handleItemDelete.bind(this);
}
render() {
return (
<Fragment>
<div>
<label htmlFor="insertArea">输入内容</label>
<input
id="insertArea"
className="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
/>
<button onClick={this.handleBtnClick}>提交</button>
</div>
<ul>
{
this.getTodoItem()
}
</ul>
</Fragment>
)
}
//使用axios
componentDidMount(){
axios.get('/api/todolist')
.then(()=>{alert('succ')})
.catch(()=>{alert('error')})
}
//构建一个ref引用,这个引用叫this.input指向对应的input dom节点
getTodoItem(){
return this.state.list.map((item,index)=>{
return (
<TodoItem
key={index}
content={item}
index={index}
deleteItem={this.handleItemDelete}
/>
)
})
}
handleInputChange(e){
// console.log(e.target) //其实就是dom节点 <input id="insertArea" class="input" value="">
// const value = e.target.value //在外层保存一下
const value = e.target.value //e.target使用this.input来替换
this.setState(()=>({
inputValue: value
}));
}
handleBtnClick(){
this.setState((prevState)=>({
list:[...prevState.list,prevState.inputValue],
inputValue:''
}));
}
handleItemDelete(index){
this.setState((prevState)=>{
const list = [...prevState.list];
list.splice(index,1)
return {list}
});
}
}
export default Todolist;
使用Charles实现本地数据mock
前后端分离,需要前端本地进行接口数据的模拟
下载安装charles工具
桌面创建todolist.json,希望发送todolist接口的时候,能够把桌面上的todolist.json返回回来
todolist.json
["学习React","学习英语","学习Vue"]
需要借助charles工具来解决这个问题
charles工具=>Tools=>Map Local
add 增加一条配置
charles可以抓到浏览器向外发送的一些请求,然后可以对一些请求做一些处理,比如看到请求地址
http://localhost:3000/api/todolist
,只要请求这个地址,就把桌面文件返回,charles就是一个中间的代理服务器
componentDidMount(){
axios.get('/api/todolist')
.then((res)=>{
console.log(res.data)
// this.setState(()=>{
// return {
// list: res.data
// }
// })
this.setState(()=>({
list:[...res.data]
/构建一个新的数组传递,避免不小心把res.data做了修改/
}))
})
.catch(()=>{alert('error')})
}
十:React中实现CSS过渡动画
十一:Redux入门
想做大型应用,需要在React基础上配套一个数据层框架结合使用,全球范围内比较好的搭配数据层框架——Redux。
Redux基础设计理念:把组件之中的数据放到公用存储区域存储,组件改变数据不需要传递,改变Store里的数据之后,其他组件会感知到Store数据发生改变,再去获取数据,这样不管组件层次有多深,走的流程相同。
Redux = Reducer + Flux
Redux的起源:React在2013年开源时,Facebook团队除了放出React框架,还放出了一个框架Flux,Flux框架是官方推出的最原始辅助React使用的数据层框架。业界使用Flux发现一些缺点,比如公共存储区域Store可以有很多store组成,数据存储的时候可能存在数据依赖问题,总之不是特别好用,有人把Flux做了一个升级,升级成了目前使用的Redux。
在Redux里面除了借鉴Flux以前很多设计理念之外,又引入了Reducer概念。
Redux的工作流程:
- Store存放了所有数据(存储数据的公共区域)
- React Components(组件)从Store里拿数据,每个组件也要去改Store里数据
流程简述一:
流程简述二:
首先由一个组件,组件要去获取Store里一些数据,和Store说"我要获取数据"这句话就是Action Creators,Action Creators创建了一句话之后告诉Store,Store接收到"我要获取数据",Store并不知道需要什么样的数据,他去查一下,Reducers知道应该给你什么样的数据,Reducers告诉Store应该给组件什么样的数据,Store知道了之后把数据给到组件。
使用Antd美化TodoList页面:
Antd是React的UI框架。
yarn add antd
引入样式:
import 'antd/dist/antd.css';
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
const data = [
'Racing car sprays burning fuel into crowd.',
'Japanese princess to wed commoner.',
'Australian walks 100km after outback crash.',
'Man charged over missing wedding girl.',
'Los Angeles battles huge wildfires.',
];
class TodoList extends Component {
render() {
return (
<div style={{marginTop: '10px',marginLeft: '10px'}}>
<div>
<Input placeholder="todo info" style={{width: '300px',marginRight:'10px'}} />
<Button type="primary">提交</Button>
</div>
<List
style={{marginTop: '10px',width:'300px'}}
bordered
dataSource={data}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
}
export default TodoList;
Antd工具在开发一些后台管理系统的时候用的非常多。
创建Redux中的Store:
安装Redux
yarn add redux
新建文件 src/store/index.js /仓库(图书管理员)
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer); // 把笔记本传递给store
export default store;
--------------------------------------------------------
新建文件 src/store/reducer.js /记录本
const defaultState = {
inputValue:'111',
list:[1,2]
}
export default (state=defaultState,action)=>{ //函数接受两个参数
return state;
}
/ reducer 是笔记本,笔记本存放很多关于图书馆数据操作,数据情况。
/ state 存放整个图书馆里所有书籍信息
/ state=defaultState 默认什么信息都不存储
-----------------------------------------------------------------
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
import store from './store'; /简化写法 import store from './store/index.js';
class TodoList extends Component {
constructor(props){
super(props);
this.state = store.getState();
console.log(this.state) /通过store.getState() 获取store中的数据
}
render() {
return (
<div style={{marginTop: '10px',marginLeft: '10px'}}>
<div>
<Input value={this.state.inputValue} placeholder="todo info" style={{width: '300px',marginRight:'10px'}} />
<Button type="primary">提交</Button>
</div>
<List
style={{marginTop: '10px',width:'300px'}}
bordered
dataSource={this.state.list}
renderItem={item => (<List.Item>{item}</List.Item>)}
/>
</div>
)
}
}
export default TodoList;
Action和Reducer的编写:
安装工具 拓展程序 redux devtools
安装完成之后,控制台会多出redux选项
如何使用 redux devtools
src/store/index.js文件
// 在代码中添加 window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); // 把笔记本传递给store
/ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
/ 当做第二个参数传递进去,意思是如果有这个变量就执行这个变量对应的方法
/ __REDUX_DEVTOOLS_EXTENSION__这个变量是Redux DevTools的浏览器拓展
/ 意思是如果下面安装了Redux DevTools,那么就在页面上使用这个工具
export default store;
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import { Input,Button,List } from 'antd';
import store from './store';
class TodoList extends Component {
constructor(props){
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
store.subscribe(this.handleStoreChange)
//这个组件去订阅Store,只要Store数据发生改变
//subscribe()里面写一个函数,这个函数就被自动执行
console.log(this.state) //通过store.getState() 获取store中的数据
}
render() {
return (
<div style={{marginTop: '10px',marginLeft: '10px'}}>
<div>
<Input
value={this.state.inputValue}
placeholder="todo info"
style={{width: '300px',marginRight:'10px'}}
onChange={this.handleInputChange}
/>
<Button
type="primary"
onClick={this.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{marginTop: '10px',width:'300px'}}
bordered
dataSource={this.state.list}
renderItem={(item,index) => (<List.Item onClick={this.handleItemDelete.bind(this,index)}>{item}</List.Item>)}
/>
</div>
)
}
handleInputChange(e){
const action = {
type:'change_input_value',
value: e.target.value
}
store.dispatch(action)
// 把action 传递给Store
// Store需要去查小手册(Reducers),把当前数据和action一起传递给小手册(Reducers)
// Store会把当前Store存的数据,和接收到的action一起转发给reducers
// reducers来告诉Store来做什么
}
handleStoreChange(){
this.setState(store.getState())
//当感知到Store数据发生改变,就调用store.getState(),从store里从新取一次数据
//再调用setState 替换当前组件数据
}
handleBtnClick(){
const action = {
type:'add_todo_item'
}
store.dispatch(action)
}
handleItemDelete(index){
const action = {
type:'delete_todo_item',
index
}
store.dispatch(action)
}
}
export default TodoList;
src/store/reducer.js
const defaultState = {
inputValue:'',
list:[1,2]
}
//为什么需要深拷贝?
//Redux的限制,reducer可以接受state,但绝不能修改state
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 //返回给Store,替换Store的老数据
}
if(action.type === "add_todo_item"){
const newState = JSON.parse(JSON.stringify(state)) //深拷贝
newState.list.push(newState.inputValue);
newState.inputValue="";
return newState //返回给Store,替换Store的老数据
}
if(action.type === "delete_todo_item"){
const newState = JSON.parse(JSON.stringify(state)) //深拷贝
newState.list.splice(action.index,1);
return newState //返回给Store,替换Store的老数据
}
return state;
}
// reducer 是笔记本,笔记本存放很多关于图书馆数据操作,数据情况。
// state 存放整个图书馆里所有书籍信息
// state=defaultState 默认什么信息都不存储
// state指的是上一次存储数据
// action指的是用户传过来的那句话
ActionTypes的拆分:
新建src/store/actionTypes.js 文件存放 action Type
export const CHANGE_INPUT_VALUE = 'change_input_value'; //export 导出
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
使用:
src/TodoList.js
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from './store/actionTypes';
CHANGE_INPUT_VALUE 替换 'change_input_value'
ADD_TODO_ITEM 替换 'add_todo_item'
DELETE_TODO_ITEM 替换 'delete_todo_item'
src/store/reducer.js
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM} from './actionTypes';
CHANGE_INPUT_VALUE 替换 'change_input_value'
ADD_TODO_ITEM 替换 'add_todo_item'
DELETE_TODO_ITEM 替换 'delete_todo_item'
使用actionCreator 统一创建 action
新建 src/store/actionCreators.js
import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM } from './actionTypes';
export const getInputChangeAction = (value)=> ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddItemAction = ()=> ({
type: ADD_TODO_ITEM
})
export const getDeleteItemAction = (index)=> ({
type: DELETE_TODO_ITEM,
index
})
使用:
src/TodoList.js
import { getInputChangeAction,getAddItemAction,getDeleteItemAction } from './store/actionCreators'
handleInputChange(e){
// const action = {
// type: CHANGE_INPUT_VALUE,
// value: e.target.value
// }
const action = getInputChangeAction(e.target.value)
store.dispatch(action)
}
handleBtnClick(){
// const action = {
// type: ADD_TODO_ITEM
// }
const action = getAddItemAction()
store.dispatch(action)
}
handleItemDelete(index){
// const action = {
// type: DELETE_TODO_ITEM,
// index
// }
const action = getDeleteItemAction(index)
store.dispatch(action)
}
好处:
- 提高代码可维护性
- 方便前端自动测试化工具
Redux 知识点复习补充
Redux 设计和使用的三项原则:
- Store是唯一的
整个项目只有一个store 在src/store/index.js
(整个应用中只有一个store公共存储空间)
- 只有store能够改变自己的内容(在reducer中改变了store的数据是不被允许的,在reducer中是复制一个新的对象进行操作)
- Reducer必须是纯函数
纯函数指的是,给定固定的输入,就一定会有固定的输出,而且不会有任何副作用
state和action都确定的时候,那么return出来的结果永远都是固定的,不纯的函数,比如newState.inputValue = new Date(),因为return newState的值不是固定的
副作用指 state.inputValue = action.value,这段代码就是有副作用的代码,对接受的参数做了修改
Redux 核心API
- createStore 创建store
- store.dispatch 派发action(action传递给Store)
- store.getState 获取store数据内容
- store.subscribe 订阅store的改变
十二:Redux进阶
UI组件和容器组件
UI组件(傻瓜组件)- 只负责页面的一些显示
容器组件(聪明组件)- 负责页面逻辑处理
划分组件的原因:把组件的逻辑和渲染放到一个组件中管理维护起来比较困难。或内容比较多。需要对组件进行一个拆分。
- UI组件负责页面渲染
- 容器组件负责页面逻辑
创建UI组件
src/TodoListUI.js
import React, { Component } from 'React';
import { Input,Button,List } from 'antd';
class TodoListUI extends Component {
render() {
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value={this.props.inputValue}
placeholder="todo info"
style={{ width: '300px', marginRight: '10px' }}
onChange={this.props.handleInputChange}
/>
<Button
type="primary"
onClick={this.props.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{ marginTop: '10px', width: '300px' }}
bordered
dataSource={this.props.list}
renderItem={(item, index) => (<List.Item onClick={()=>{this.props.handleItemDelete(index)}}>{item}</List.Item>)}
/>
</div>
)
}
}
export default TodoListUI;
创建一个容器组件
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction } from './store/actionCreators'
import TodoListUI from './TodoListUI';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
this.handleItemDelete = this.handleItemDelete.bind(this)
store.subscribe(this.handleStoreChange)
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
/>
)
}
handleInputChange(e) {
const action = getInputChangeAction(e.target.value)
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState())
}
handleBtnClick() {
const action = getAddItemAction()
store.dispatch(action)
}
handleItemDelete(index) {
const action = getDeleteItemAction(index)
store.dispatch(action)
}
}
export default TodoList;
无状态组件
上面的UI组件(傻瓜组件)只有一个render函数,就可以用无状态组件来定义这个组件。
无状态组件如何定义?
无状态组件其实就是一个函数。
src/TodoListUI.js 改写 UI组件改成无状态组件
import React, { Component } from 'react';
import { Input,Button,List } from 'antd';
const TodoListUI = (props)=>{
// {/* this.props.inputValue 改成props.inputValue */}
// {/*不需要this 从传参获取*/}
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value={props.inputValue}
placeholder="todo info"
style={{ width: '300px', marginRight: '10px' }}
onChange={props.handleInputChange}
/>
<Button
type="primary"
onClick={props.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{ marginTop: '10px', width: '300px' }}
bordered
dataSource={props.list}
renderItem={(item, index) => (<List.Item onClick={()=>{props.handleItemDelete(index)}}>{item}</List.Item>)}
/>
</div>
)
}
//接收一个props 同时要求返回一个JSX
// class TodoListUI extends Component {
// render() {
// return (
// <div style={{ marginTop: '10px', marginLeft: '10px' }}>
// <div>
// <Input
// value={this.props.inputValue}
// placeholder="todo info"
// style={{ width: '300px', marginRight: '10px' }}
// onChange={this.props.handleInputChange}
// />
// <Button
// type="primary"
// onClick={this.props.handleBtnClick}
// >
// 提交
// </Button>
// </div>
// <List
// style={{ marginTop: '10px', width: '300px' }}
// bordered
// dataSource={this.props.list}
// renderItem={(item, index) => (<List.Item onClick={()=>{this.props.handleItemDelete(index)}}>{item}</List.Item>)}
// />
// </div>
// )
// }
// }
export default TodoListUI;
当一个普通组件只有render函数,可以使用无状态组件替换掉普通组件
优点:无状态组件性能比较高。
无状态组件就是一个函数,而class类生成对象还会有一些生命周期函数,执行起来需要执行render和生命周期函数,执行的东西远比函数执行的多。
注:虽然理论上UI组件只做页面渲染,有时候简单做一些逻辑也是可以的。
Redux中发送异步请求获取数据
componentDidMount 中获取数据
todolist.json
["学习React","学习英语","学习Vue"]
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList';
ReactDOM.render(<TodoList />, document.getElementById('root'));
src/TodoList.js
import React, { Component } from 'react';
import 'antd/dist/antd.css';
import store from './store';
import { getInputChangeAction, getAddItemAction, getDeleteItemAction,initListAction } from './store/actionCreators'
import TodoListUI from './TodoListUI';
import axios from 'axios'; //引入第三方请求组件
class TodoList extends Component {
constructor(props) {
super(props);
this.state = store.getState();
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
this.handleItemDelete = this.handleItemDelete.bind(this)
store.subscribe(this.handleStoreChange)
//这个组件去订阅Store,只要Store数据发生改变
//subscribe()里面写一个函数,这个函数就被自动执行
console.log(this.state) //通过store.getState() 获取store中的数据
}
render() {
return (
<TodoListUI
inputValue={this.state.inputValue}
list={this.state.list}
handleInputChange={this.handleInputChange}
handleBtnClick={this.handleBtnClick}
handleItemDelete={this.handleItemDelete}
/>
)
}
componentDidMount(){
axios.get('/todolist.json').then((res)=>{ //成功函数
const data = res.data; //接口获取到数据,改变store中的数据
//store有个方法 dispatch 先要创建一个action
const action = initListAction(data)
store.dispatch(action)
//把action传递给store ,store拿到之前的数据连同action一同传递给reducer
console.log(data)
}).catch((fail)=>{ //失败函数
})
}
handleInputChange(e) {
// const action = {
// type: CHANGE_INPUT_VALUE,
// value: e.target.value
// }
const action = getInputChangeAction(e.target.value)
store.dispatch(action)
// 把action 传递给Store
// Store需要去查小手册(Reducers),把当前数据和action一起传递给小手册(Reducers)
// Store会把当前Store存的数据,和接收到的action一起转发给reducers
// reducers来告诉Store来做什么
}
handleStoreChange() {
this.setState(store.getState())
//当感知到Store数据发生改变,就调用store.getState(),从store里从新取一次数据
//再调用setState 替换当前组件数据
}
handleBtnClick() {
// const action = {
// type: ADD_TODO_ITEM
// }
const action = getAddItemAction()
store.dispatch(action)
}
handleItemDelete(index) {
// const action = {
// type: DELETE_TODO_ITEM,
// index
// }
const action = getDeleteItemAction(index)
store.dispatch(action)
}
}
export default TodoList;
src/TodoListUI.js
import React, { Component } from 'react';
import { Input,Button,List } from 'antd';
const TodoListUI = (props)=>{
// {/* this.props.inputValue 改成props.inputValue */}
// {/*不需要this 从传参获取*/}
return (
<div style={{ marginTop: '10px', marginLeft: '10px' }}>
<div>
<Input
value={props.inputValue}
placeholder="todo info"
style={{ width: '300px', marginRight: '10px' }}
onChange={props.handleInputChange}
/>
<Button
type="primary"
onClick={props.handleBtnClick}
>
提交
</Button>
</div>
<List
style={{ marginTop: '10px', width: '300px' }}
bordered
dataSource={props.list}
renderItem={(item, index) => (<List.Item onClick={()=>{props.handleItemDelete(index)}}>{item}</List.Item>)}
/>
</div>
)
}
//接收一个props 同时要求返回一个JSX
// class TodoListUI extends Component {
// render() {
// return (
// <div style={{ marginTop: '10px', marginLeft: '10px' }}>
// <div>
// <Input
// value={this.props.inputValue}
// placeholder="todo info"
// style={{ width: '300px', marginRight: '10px' }}
// onChange={this.props.handleInputChange}
// />
// <Button
// type="primary"
// onClick={this.props.handleBtnClick}
// >
// 提交
// </Button>
// </div>
// <List
// style={{ marginTop: '10px', width: '300px' }}
// bordered
// dataSource={this.props.list}
// renderItem={(item, index) => (<List.Item onClick={()=>{this.props.handleItemDelete(index)}}>{item}</List.Item>)}
// />
// </div>
// )
// }
// }
export default TodoListUI;
src/store/actionCreators.js
import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM,INIT_LIST_ACTION } from './actionTypes';
export const getInputChangeAction = (value)=> ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddItemAction = ()=> ({
type: ADD_TODO_ITEM
})
export const getDeleteItemAction = (index)=> ({
type: DELETE_TODO_ITEM,
index
})
export const initListAction = (data) => ({ //接受list.json接口数据
type: INIT_LIST_ACTION,
data
})
src/store/actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value'; //export 导出
export const ADD_TODO_ITEM = 'add_todo_item';
export const DELETE_TODO_ITEM = 'delete_todo_item';
export const INIT_LIST_ACTION = 'init_list_action'; //接受list.json 定义常量
src/store/index.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); // 把笔记本传递给store
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
// 当做第二个参数传递进去,意思是如果有这个变量就执行这个变量对应的方法
// __REDUX_DEVTOOLS_EXTENSION__这个变量是Redux DevTools的浏览器拓展
// 意思是如果下面安装了Redux DevTools,那么就在页面上使用这个工具
export default store;
src/store/reducer.js
import {CHANGE_INPUT_VALUE,ADD_TODO_ITEM,DELETE_TODO_ITEM,INIT_LIST_ACTION} from './actionTypes';
const defaultState = {
inputValue:'',
list:[]
}
//为什么需要深拷贝?
//Redux的限制,reducer可以接受state,但绝不能修改state
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 //返回给Store,替换Store的老数据
}
if(action.type === ADD_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state)) //深拷贝
newState.list.push(newState.inputValue);
newState.inputValue="";
return newState //返回给Store,替换Store的老数据
}
if(action.type === DELETE_TODO_ITEM){
const newState = JSON.parse(JSON.stringify(state)) //深拷贝
newState.list.splice(action.index,1);
return newState //返回给Store,替换Store的老数据
}
if(action.type === INIT_LIST_ACTION){
const newState = JSON.parse(JSON.stringify(state)) //深拷贝
newState.list = action.data
return newState //返回给Store,替换Store的老数据
}
return state;
}
// reducer 是笔记本,笔记本存放很多关于图书馆数据操作,数据情况。
// state 存放整个图书馆里所有书籍信息
// state=defaultState 默认什么信息都不存储
// state指的是上一次存储数据
// action指的是用户传过来的那句话
使用Redux-thunk中间件进行ajax请求发送
ajax异步或者复杂逻辑放在组件里实现时,组件会显得过于臃肿,希望移除到其他地方统一管理。
Redux-thunk中间件可以把异步请求或复杂逻辑放到action中处理
Redux-thunk是Redux的一个中间件。
在React中Redux-thunk这个中间件用的非常多。
Redux-thunk:github地址
yarn add redux-thunk //安装
使用 在store中
import { createStore, applyMiddleware } from 'redux'; 要引入 applyMiddleware
import thunk from 'redux-thunk'; //引入redux-thunk模块
import rootReducer from './reducers/index';
// Note: this API requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(thunk) //第二个参数引入
);
意思是 创建Store时,使用reducer构建初始数据,然后创建store时,store会使用一个中间件thunk
我们说的中间件,是redux的中间件,不是react的
引入多个中间件
引入thunk 以及 redux-devtools
githup 搜索 redux-devtools 查到文档
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(...middleware),
// other store enhancers if any
);
使用
通过这种编码,既支持window下的devtools,同时也引入了Redux-thunk
src/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;
// 如果window的__REDUX_DEVTOOLS_EXTENSION_COMPOSE__存在,就去调用一下这个方法
// 否则就等于一个compose函数,compose函数需要从redux中引入进来
const enhancer = composeEnhancers( //让上面存储的composeEnhancers,执行一下
// 顺便把thunk通过applyMiddleware执行一下,传递进去
applyMiddleware(thunk),
);
const store = createStore(reducer, enhancer);
// 实际上 __REDUX_DEVTOOLS_EXTENSION__ 也是redux的中间件
// 我们说的中间件,是redux的中间件
export default store;
上面代码只做了一件事,安装了thunk,然后在store创建的时候,使用了thunk,代码从GitHub对应的指南里面拷贝过来的
配置好了之后,围绕redux-thunk来编写代码
src/TodoList.js
把异步操作的代码从组件中移除,移除到action里面
import { getInputChangeAction, getAddItemAction, getDeleteItemAction,getTodoList } from './store/actionCreators'
// 引入getTodoList
componentDidMount(){
const action = getTodoList();
store.dispatch(action)
/dispatch了一个函数,实际上store只能接受一个对象
/store发现是一个函数就会干一件事,帮助自动执行一下对应的函数
/action对应的函数是actionCreators.js 中getTodoList 的 return 函数
// axios.get('/todolist.json').then((res)=>{
// const data = res.data;
// const action = initListAction(data)
// store.dispatch(action)
// }).catch((fail)=>{})
// 把异步操作的代码从组件中移除,移除到action里面
}
当使用了Readux-thunk之后 action可以是一个函数了(只有用了thunk之后,才能是函数)
getTodoList 的 rerun 函数再去取json的数据,获取数据,去改变store中的数据
只要改变store中的数据,又要走redux的流程
去调用之前写的initListAction,去创建action
想去调用store.dispatch方法
store.dispatch方法如何获取到?
返回的函数自动就会接收dispatch方法
只要调用dispatch 把 action派发出去就行
这个action 实际是一个对象
store会判断action是一个对象,直接就接收这个对象,改变原始状态
src/store/actionCreators.js
import axios from 'axios'; //引入第三方请求组件
/* ------------未变化------------- */
import { CHANGE_INPUT_VALUE,ADD_TODO_ITEM, DELETE_TODO_ITEM,INIT_LIST_ACTION } from './actionTypes';
export const getInputChangeAction = (value)=> ({
type: CHANGE_INPUT_VALUE,
value
})
export const getAddItemAction = ()=> ({
type: ADD_TODO_ITEM
})
export const getDeleteItemAction = (index)=> ({
type: DELETE_TODO_ITEM,
index
})
export const initListAction = (data) => ({ //接受list.json接口数据
type: INIT_LIST_ACTION,
data
})
/* ------------未变化------------- */
// 当使用了Readux-thunk之后 action可以是一个函数了
export const getTodoList = () => { //调用getTodoList生成内容是函数的action时
return (dispatch) => { // 这个函数能够接收到dispatch方法
axios.get('/todolist.json').then((res) => {
const data = res.data;
const action = initListAction(data)
dispatch(action) //直接调用dispatch方法即可
}).catch((fail)=>{})
}
// 正常来说,return应该是一个对象 但使用了Readux-thunk之后,return结果可以是一个函数
// 在这个函数里面 可以做异步操作
}
疑问:把ajax放到componentDidMount中不是挺好的,为什么要这么麻烦
如果把异步函数放到组件的生命周期函数中来做,这个生命周期函数有可能变得越来越复杂,越来越多,这个组件会变得越来越大,所以建议把复杂的业务逻辑或异步函数拆分到一个地方去管理,现在借助Redux-thunk就可以放到actionCreators中去管理了
放到这里管理又带来一个额外好处,做自动化测试的时候,去测试getTodoList方法会非常的简单,比测试组件的生命周期函数要简单的多。
反复写5遍中间件的使用流程,捋清流程