react

这篇博客详细介绍了如何使用React和Redux构建TodoList应用,涵盖了环境准备、工程目录结构、jsx语法、React组件、事件绑定、状态管理和生命周期函数等内容。通过TodoList的实现,讲解了React的响应式设计、事件处理、组件拆分和数据传递。同时,还涉及到了Redux的工作流程、Action和Reducer的编写、使用Antd美化页面以及如何进行异步数据请求。最后,讨论了Redux的中间件如Redux-thunk和Redux-saga的使用。
摘要由CSDN通过智能技术生成

react的使用
引入.js文件使用react
通过脚手架工具来编码
create-react-app

环境准备

安装好node环境 安装npm
查看安装版本
node -v 
npm -v
安装create-react-app
npm install -g create-react-app

//换源
npm config set registry https://registry.npm.taobao.org
//配置后通过以下方法验证是否成功
npm config get registry

创建一个react-app
create-react-app todolist

工程目录文件简介

在这里插入图片描述

index.js是整个程序运行的入口文件

js中可以引入css文件的

import React from 'react';
import ReactDOM from 'react-dom';
// all in js 在js中引入css文件
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
reportWebVitals();

简便工程代码

简化src的代码只留下,index.js,App.js
App.js

import React from "react";
function App() {
  return (
    <div>
      hello react
    </div>
  );
}

export default App;

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

在这里插入图片描述

react中的组件

这里App.js中的App就是一个组件
低版本react中的App.js
在这里插入图片描述

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

// 把app组件挂载到id为root的节点下
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

在这里插入图片描述

jsx语法

在js中写的html标签称为jsx语法
jsx语法中 如果要使用自己的组件 则用标签包起来,且组件必须以大写开头

react基础知识

使用react编写todolist功能

TodoList.js

import React from "react";
function TodoList() {
    return (
        <div>
        <div>
            <input/>
            <button>提交</button>
        </div>
            <ul>
                <li>学英语</li>
                <li>study react</li>
            </ul>
        </div>
    );
}
export default TodoList;

jsx语法要求最外层需要包裹一层div
可以使用Fragment占位符替代,Fragment不会显示出来

import React, {Fragment} from "react";
function TodoList() {
    return (
        <Fragment>
        <div>
            <input/>
            <button>提交</button>
        </div>
            <ul>
                <li>学英语</li>
                <li>study react</li>
            </ul>
        </Fragment>
    );
}
export default TodoList;

react中的响应式设计思想和事件绑定

操作数据而不是操作dom
TodoList.js

import React, {Fragment,Component} from "react";
class  TodoList extends Component{
    constructor(props) {
        super(props);
        this.state = {
            inputValue:'',
            list:[]
        }
    }
    render() {
    return (
        <Fragment>
        <div>
        
        <input onChange={this.inputChange.bind(this)}
            value = {this.state.inputValue}/>
        <button>提交</button>
        </div>
        <ul>
        <li>学英语</li>
        <li>study react</li>
        </ul>
        </Fragment>
        );
    }

    inputChange(e) {
        console.log(e.target.value)
        // 设置state中的inputValue的值
        this.setState({
            inputValue:e.target.value
        })
    }
    }
export default TodoList;

新增删除列表项

新增列表项
给button绑定事件,在原有数组基础上,将输入框中的值添加到数组中,然后遍历数组中的每一项的值,返回li标签

<input onChange={this.inputChange.bind(this)}
            value = {this.state.inputValue}/>
        <button onClick={this.btnClick.bind(this)}>提交</button>
        </div>

btnClick() {
        this.setState({
            list:[...this.state.list,this.state.inputValue],
            inputValue:''
        })
    }
<ul>
            {
                this.state.list.map( (item,index) =>{
                    return <li key={index} onClick={this.btnDelete.bind(this,index)}>{item}</li>
                })
            }
        </ul>

删除列表项
给li标签绑定btnDelete事件,并绑定数组下标index,传入下标,我们一般不去改变原有的state,而是单独拷贝一份出来进行修改
然后通过setState方法设置list的值

btnDelete(index) {
        // 不去改变原有的state 而是相当于拷贝一份出来
        const list = [...this.state.list]
      list.splice(index,1)
        this.setState({
            list:list
        })
    }

jsx语法细节补充

小写字母开头标签,会作为普通标签。当遇到大写字母开头的标签会认为是组件

jsx中注释写法{/111/}
{
//注释
}

如果要是标签的内容不被转义,使用dangerouslySetInnerHTML

						<li
                            key={index}
                            onClick={this.btnDelete.bind(this,index)}
                            // {}中一般是一个js表达式,{{__html:item}}表达式中又是一个对象
                            dangerouslySetInnerHTML={{__html:item}}
                        ></li>

使用label能够起到扩大输入的范围

		<div>
            <label htmlFor="insertarea">输入内容</label>
        <input
            id="insertarea"
            className='input'
            onChange={this.inputChange.bind(this)}
            value = {this.state.inputValue}/>
         </div>

在js中引入index.css

.input {
    border: 1px solid red;
}
import './index.css'
<input
            id="insertarea"
            className='input'
            onChange={this.inputChange.bind(this)}
            value = {this.state.inputValue}/>
        <button onClick={this.btnClick.bind(this)}>提交</button>
        </div>

拆分组件与组件之间的传值问题

父组件向子组件传递数据

新建TodoItem.js组件

import React,{Component} from 'react';
class TodoItem extends Component {
    render() {
        return <li>{this.props.content}</li>
    }
}

export default TodoItem

修改TodoList.js组件

<ul>
            {
                this.state.list.map( (item,index) =>{
                    return (
                        <div>
                            <TodoItem content={item}/>

                        {/*
                            <li
                            key={index}
                            onClick={this.btnDelete.bind(this,index)}

                            dangerouslySetInnerHTML={{__html:item}}
                        ></li>

                        */}
                        </div>
                    )
                })
            }
        </ul>

注意
父组件向子组件传递内容,通过属性的形式来传递content={item}
子组件通过this.props.content来接收传递过来的content属性

子组件向父组件传递数据

那么子组件如何向父组件传递数据呢

我想点击删除数据,本质上是修改list,但是子组件是不允许直接修改父组件数据的

首先我们需要获取数组每一项的下标,通过父组件传递给子组件

<TodoItem content={item} index={index}/>

然后子组件如何调用父组件的btndelete方法去改变父组件里面的数据

父组件通过属性deleteItem 将方法btnDelete传递给子组件

<TodoItem content={item} index={index} deleteItem = {this.btnDelete}/>

子组件中调用父组件中的数据以及方法

import React,{Component} from 'react';
class TodoItem extends Component {
    constructor(props) {
        super(props);

    }
    render() {
        return <li onClick={this.handleClick.bind(this)}>{this.props.content}</li>
    }

    handleClick() {
        this.props.deleteItem(this.props.index)
    }

}

export default TodoItem

这里this.handleClick.bind(this)可以通过另一种方式绑定节省性能

constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this)
    }

原来的通过属性deleteItem传递给子组件的方法btnDelete,他的指向应当指向父组件TodoList
<TodoItem content={item} index={index} deleteItem = {this.btnDelete.bind(this)}/>

handleClick() {
        this.props.deleteItem(this.props.index)
        //这里相当于this.btnDelete() 这里的this要指向TodoList,而不是TodoItem
    }

TodoList代码优化

对代码进行解构赋值

render() {
        // 通过es6语法对代码进行解构赋值
        const {content} = this.props
        return <li onClick={this.handleClick}>{content}</li>
    }

    handleClick() {
        const {deleteItem,index} = this.props
        deleteItem(index)
    }

将this指向在constructor中定义好

    constructor(props) {
        super(props);
        this.state = {
            inputValue:'',
            list:[]
        }
        this.inputChange = this.inputChange.bind(this)
        this.btnClick = this.btnClick.bind(this)
        this.btnDelete = this.btnDelete.bind(this)
    }

将逻辑代码通过方法进行封装

	<ul>
     {this.getItem()}
 	</ul>

getItem() {
        return this.state.list.map( (item,index) =>{
            return (
                <div>
                    <TodoItem content={item} index={index} deleteItem = {this.btnDelete}/>
                </div>
            )
        })
    }

setState数据 之前是传入对象
现在新版是传入函数,在函数中传入对象,从而setState变成了一个异步的setState
不能直接传入e,需要对其进行保存

inputChange(e) {
        console.log(e.target.value)
        const value = e.target.value
        this.setState(() =>{
            return {
                inputValue: value
            }
        })
        // this.setState({
        //     inputValue:e.target.value
        // })
    }

这里prevState是修改前的一次state

btnClick() {
        this.setState((prevState) =>{
            return {
                list:[...prevState.list,prevState.inputValue],
                inputValue:''
            }
        })

        // this.setState({
        //     list:[...this.state.list,this.state.inputValue],
        //     inputValue:''
        // })
    }
btnDelete(index) {
        // 不去改变原有的state 而是相当于拷贝一份出来
      //   const list = [...this.state.list]
      // list.splice(index,1)
      //   this.setState({
      //       list:list
      //   })
        this.setState((prevState) => {
            const list = [...prevState.list]
            list.splice(index,1)
            return {
                list
            }
        })
    }

解决key的警告
有div则写在循环的最外层元素上
这里div是为了包裹之前的注释代码的 这里可以将div删除 将key写在TodoItem组件上
<TodoItem key={index} content={item} index={index} deleteItem = {this.btnDelete}/>

 getItem() {
        return this.state.list.map( (item,index) =>{
            return (
                <div key={index}>
                    <TodoItem content={item} index={index} deleteItem = {this.btnDelete}/>
                </div>
            )
        })
    }

react的一些特性

直接操作dom 命令式编程
声明式开发 react
可以并存其它框架
只管<div id="root"></div>的渲染
我们可以在Index中编写其它dom
组件化
单向数据流 父组件向子组件传值(list),子组件可以使用这个值,但是不能改变它
视图层框架
函数式编程

安装react开发调试工具

翻墙安装react-developer-tools
在这里插入图片描述

PropTypes与DefaultProps的应用

PropTypes
子组件接收父组件传值
对它传过来的数据的类型进行强校验

render() {
        // 通过es6语法对代码进行解构赋值
        const {content,test} = this.props
        return <li onClick={this.handleClick}>
            {test} - {content}
        </li>
    }

TodoItem.propTypes = {
	test:PropTypes.string.isRequired,
    // 要求content必须传递
    // content:PropTypes.string.isRequired,
    //content是一个number类型或者string类型
    //content:PropTypes.arrayOf(PropTypes.string,PropTypes.number),
    content:PropTypes.string,
    deleteItem:PropTypes.func,
    index:PropTypes.number
}
TodoItem.defaultProps = {
    // 当test没有传值时 给它一个默认值
    test:'hello'
}

Props State 与render函数

当组件的state或者props发生改变的时候,render函数就会重新执行
之所以数据发生变化,页面跟着变化,是因为页面是由render函数渲染出来的
state变化的时候,render函数就会重新执行一次 页面跟着变化

Test.js
1.另一方面当Props发生变化时render函数也会执行
2.当父组件的render函数被运行时,它的子组件的render都将被重新运行

import React,{Component} from 'react'
class Test extends Component {
    render() {
        // 当父组件的render函数被运行时,它的子组件的render都将被重新运行
        console.log('test render')
        return <div>{this.props.content}</div>
    }
}

export default Test

React中的虚拟DOM

1.首先要有state 数据
2.然后jsx 模板
3.将数据结合模板 生成真实的DOM 渲染到页面上
4.state 发生改变
5.数据结合模板 生成真实的DOM 替换原始的DOM

缺点:
第一次生成了一个完整的Dom片段
第二次生成了一个完整的DOM片段
第二次的dom替换第一次的dom 非常耗性能
假如第二次只是改变模板中的部分代码,但是需要替换整个dom 影响性能

改良:
1.首先要有state 数据
2.然后jsx 模板
3.将数据结合模板 生成真实的DOM 渲染到页面上
4.state 发生改变
5.数据结合模板 生成真实的DOM 并不直接替换原始的DOM
6.新的dom和原始的dom做比对,找差异
7.找出input框中发生了变化
8.只用新的dom中的input元素 替换掉老的dom中的Input元素

缺点:
性能的提升并不明显

1.首先要有state 数据
2.然后jsx 模板
3.将数据结合模板 生成真实的DOM 渲染到页面上
<div id='abc'><span>hello world</span></div>
4.生成虚拟dom(虚拟dom就是一个js对象,用它来描述真实的dom) (js生成一个js对象损耗性能较低 而js创建dom 损耗性能较高)
['div',{id:'abc'},['span',{},'hello world']]
5.state 发生改变
6.生成新的虚拟dom 假设数据发生变化
['div',{id:'abc'},['span',{},'bye bye']]
7.比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中的内容(提升性能)
8.直接操作DOM 改变span中的内容

深入理解虚拟DOM

然而实际上的过程是:
1.首先要有state 数据
2.然后jsx 模板
3.数据+模板生成虚拟dom(虚拟dom就是一个js对象,用它来描述真实的dom) (js生成一个js对象损耗性能较低 而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中的内容

jsx->createElement->js对象(虚拟dom)->真实的dom

render() {
        // 通过es6语法对代码进行解构赋值
        const {content,test} = this.props
        return <div>item</div>
    }

这里的模板可以用
React.createElement('div',{},'item')替代

虚拟dom的好处
1.性能提升
2.跨端应用得以实现 React Native

虚拟DOM的Diff算法

比较原始虚拟DOM和新的虚拟DOM的区别
底层可以吧多次setState结合成一次setState
归根结底是setState发生变化时 ,并且setState是异步方法 提高了底层的性能

比对是同层比对(虚拟比对速度快)
如果第一层有变化
则对下面的都进行替换
在这里插入图片描述
列表循环引入key 是为了提高虚拟dom比对的性能
之所以不建议key值使用index是因为
是因为没有办法保证原始的虚拟DOM上的key值和新的虚拟DOM上的key值一致
a 0 b 1 c 2
删除a,下标变化
b 0 c 1

用item作为key值
a a b b c c
删除a
b b c c
在这里插入图片描述

React中的ref使用

之前代码中content:PropTypes.arrayOf(PropTypes.string,PropTypes.number)
arrayOf指的是content是个数组 数组的内容可以是string类型或者是number类型

这里应该使用oneOfType 指以下类型中的一种
content:PropTypes.oneOfType([PropTypes.string,PropTypes.number])

inputChange(e) {
        console.log(e.target)
        const value = e.target.value
        this.setState(() =>{
            return {
                inputValue: value
            }
        })

这里的e.target既是input dom元素

可以通过ref获取

<input
            id="insertarea"
            className='input'
            onChange={this.inputChange}
            value = {this.state.inputValue}
            ref={(input) =>{this.ref=input}}
        />

创建一个ref引用,this.ref指向input dom节点

直接操作dom时会遇到的问题

<ul ref={(ul) =>{this.ul = ul}}>
            {this.getItem()}
        </ul>
btnClick() {
        this.setState((prevState) =>{
            return {
                list:[...prevState.list,prevState.inputValue],
                inputValue:''
            }
        },() => {
            console.log(this.ul.querySelectorAll('ul').length)
        })

//不能在这里直接输出 因为setState是异步函数
//可能执行的先后顺序就会变化
//console.log(this.ul.querySelectorAll('ul').length
    }

正确的做法是在setState传入第二个参数,传入一个回调函数,这个回调函数会等setState异步执行完成,再进行回调

React中的生命周期函数

生命周期函数指在某一时刻组件会自动调用执行的函数
在这里插入图片描述

组件挂载渲染

 //已经不推荐使用 在组件即将被挂载到页面的时候自动执行
    componentWillMount () {
        console.log('UNSAFE component will mount')
    }

//组件被挂载到页面之后 自动执行
    componentDidMount() {
        console.log(' component did mount')
    }

只会第一次挂载的时候执行
之后不执行
—————————————————
组件更新过程
props或者states发生变化

 //数据被更新之前自动执行
    shouldComponentUpdate(nextProps, nextState, nextContext) {
        console.log('should component update')
        // 如果 return false 那么数据不会被更新
        return true;
    }

//已经弃用 组件被更新之前 它会自动执行 但是它在shouldComponentUpdate之后被执行
    //shouldComponentUpdate返回true才执行
    componentWillUpdate(nextProps, nextState, nextContext) {
        console.log('component will update')
    }

//组件更新完成之后 他会被执行
    componentDidUpdate(prevProps, prevState, snapshot) {
        console.log('componentDidUpdate')
    }
//已经弃用
    // 当一个组件从父组件接受了参数
    //只要父组件的render函数被重新执行了 子组件的这个生命周期函数就会被执行
    componentWillReceiveProps(nextProps, nextContext) {
        console.log('child componentWillReceiveProps')
    }

//当这个子组件即将被从页面剔除的时候 会被执行
    componentWillUnmount() {
        console.log('child componentWillUnmount')
    }

生命周期函数是针对组件的
每一个组件都有这些生命周期函数

生命周期函数使用场景

render生命周期函数必须要有
React.Component内置了其他的所有生命周期函数
没有内置render生命周期函数

在这里插入图片描述
父组件发生改变,render函数重新渲染,子组件的render函数也会重新渲染
然而子组件的props并没有改变,所以子组件没有必要重新渲染
会带来性能上的消耗
在子组件中加入shouldComponentUpdate

当子组件的props改变时 才重新渲染

shouldComponentUpdate(nextProps, nextState, nextContext) {
 if(nextProps != this.props.content){
            return true
        }
        return false;
    }

在react中发送ajax请求获取数据

yarn add axios

 //ajax请求放在componentDidMount中
        componentDidMount() {
            axios.get('/api/tolist')
                .then(()=>{alert('success')})
                .catch(()=>{alert('error')})
        }

使用Charles进行接口数据模拟

//ajax请求放在componentDidMount中
        componentDidMount() {
            axios.get('/api/todolist')
                .then((res)=>{
                    alert('success')
                    this.setState(()=>{
                        return{
                            list:[...res.data]
                        }

                    })
                })
                .catch(()=>{alert('error')})
        }

React实现css过渡动画

import React,{Component,Fragment} from "react";
import './style.css'
class App extends Component{
    constructor(props) {
        super(props);
       this.state={
           show:true
       }
        this.handleClick = this.handleClick.bind(this)
    }

    render() {
      return (
          <Fragment>
              <div className={this.state.show ?'show':'hide'}>hello</div>
              <button onClick={this.handleClick}>toggle</button>
          </Fragment>
      )
  }

    handleClick(){
        this.setState({
            show:this.state.show ? false : true
        })

    }
}

export default App;

.show {
    opacity: 1;
    transition: all 1s ease-in;
}

.hide {
    opacity: 0;
    transition: all 1s ease-in;
}

React中使用css的动画效果

.show {
  animation: show-item 2s ease-in forwards;
}

.hide {
    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;
    }

}

使用react-transition-group实现动画

yarn add react-transition-group

<Fragment>
              <CSSTransition
              in={this.state.show}
              timeout={1000}
              classNames='fade'
              unmountOnExit
              onEnter={(el) =>{el.style.color  = 'blue'}}
              appear={true}
              >
                  <div>hello</div>
              </CSSTransition>
              <button onClick={this.handleClick}>toggle</button>
          </Fragment>

onEnter动画第一帧的时候div这个元素的color添加蓝色
这里 unmountOnExit是隐藏元素时 会移除dom元素
appear是第一次刷新显示动画效果配合.fade-appear,.fade-appear-active

.fade-enter,.fade-appear {
    opacity: 0;
}

.fade-enter-active,.fade-appear-active {
    opacity: 1;
    transition: opacity 1s ease-in;
}

.fade-enter-done {
    opacity: 1;
    /*color: red;*/
}


.fade-exit {
    opacity: 1;
}

.fade-exit-active {
    opacity: 0;
    transition: opacity 1s ease-in;
}

.fade-exit-done {
    opacity: 0;
}

多个元素动画切换

import TransitionGroup from "react-transition-group/cjs/TransitionGroup";
<Fragment>
              <TransitionGroup>
              {
                  this.state.list.map((item) =>{
                      return (
                          <CSSTransition
                              timeout={1000}
                              classNames='fade'
                              unmountOnExit
                              onEnter={(el) =>{el.style.color  = 'blue'}}
                              appear={true}
                              key={item}
                          >
                          <div>{item}</div>
                          </CSSTransition>
                      )
                  })
              }
              </TransitionGroup>
              <button onClick={this.handleAddItem}>toggle</button>
          </Fragment>

Redux

在这里插入图片描述
数据放到store中,方便了组件之间值的传递
Redux = Reducer + Flux

Redux工作流程

在这里插入图片描述
React Component 组件 相当于借书的人
Action Creators “你要借什么书这句话”
Store:管理员
Reducer:记录本

使用antd编写TodoList页面

yarn add antd

import React,{Component} from 'react'
import {Input,Button,List } from "antd";
import "antd/dist/antd.css"

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={{marginLeft:'20px',marginTop:'20px'}}>
                <div>
                    <Input placeholder='antd design' style={{width:'300px',marginRight:'20px'}}/>
                    <Button type="primary">提交</Button>
                </div>
                <List
                    style={{width:'300px',marginTop:'20px'}}
                    bordered
                    dataSource={data}
                    renderItem={item => <List.Item>{item}</List.Item>}
                />
            </div>
        )
    }
}

export default TodoList

创建redux中的store

yarn add redux

在创建“管理员”state之前,得先告诉他管理书籍的记事本“reducer”

创建Reducer.js

const defaultState = {
    inputValue:'aaa',
    list:[1,2]
}
export default (state = defaultState,action) =>{
    //state 存储的数据 默认数据都不存储
    return state
}

创建State.js

import {createStore} from 'redux';
import reducer from "./reducer";

//可以知道reducer中的数据了 把记事本reducer传给管理员State
const store = createStore(reducer);

export default store;

TodoList.js
使用store.getState()来获取state

import store from "./store";

constructor(props) {
        super(props);
        this.state = store.getState();
    }

<div style={{marginLeft:'20px',marginTop:'20px'}}>
                <div>
                    <Input value={this.state.inputValue} placeholder='antd design' style={{width:'300px',marginRight:'20px'}}/>
                    <Button type="primary">提交</Button>
                </div>
                <List
                    style={{width:'300px',marginTop:'20px'}}
                    bordered
                    dataSource={this.state.list}
                    renderItem={item => <List.Item>{item}</List.Item>}
                />
            </div>

Action和Reducer的编写

安装插件 Redux DevTools

赋值 浅拷贝 深拷贝 理解 https://www.cnblogs.com/chengxs/p/10788442.html


store.subscribe(this.handleChangeStore)
 		<Input
       value={this.state.inputValue}
       placeholder='antd design'
       style={{width:'300px',marginRight:'20px'}}
       onChange={this.handleChange}
       />
       <Button type="primary" onClick={this.handleClick}>提交</Button>

	handleChange(e){
       const action = {
           type:'change_input_value',
           value:e.target.value
       }
       store.dispatch(action)
    }

    handleChangeStore(){
       this.setState(store.getState())
    }

    handleClick(){
        const action = {
            type:'add_list_item'
        }
        store.dispatch(action)
    }
const defaultState = {
    inputValue:'aaa',
    list:[1,2]
}

//reducer可以接收state 但是不可以修改state
export default (state = defaultState,action) =>{
    //之前的数据 与 当前的这句话的内容
    if(action.type === 'change_input_value'){
        //对state进行深拷贝
        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=''
        console.log(newState)
        return newState
    }

    console.log(state)
    return state

}

使用Redux完成TodoList的删除功能

renderItem={
 (item,index) => <List.Item  onClick={this.deleteListItem.bind(this,index)}>{item}</List.Item>}

deleteListItem(index){
        console.log(index)
        const action = {
            type: 'delete_list_item',
            index:index
        }
        store.dispatch(action)
    }
if(action.type === 'delete_list_item'){
        const newState = JSON.parse(JSON.stringify(state))
        newState.list.splice(action.index,1)
        return newState
    }

ActionTypes的拆分

export const CHANGE_INPUT_VALUE = 'change_input_value';
export const ADD_LIST_ITEM = 'add_list_item';
export const DELETE_LIST_ITEM = 'delete_list_item';

将TodoList.js和reducer.js中的变量进行替换
变量常量在代码中写错会报出异常 字符串不会报出异常

使用actionCreator统一创建action

将action抽离出来封装 提高代码可维护性

import {CHANGE_INPUT_VALUE,ADD_LIST_ITEM,DELETE_LIST_ITEM} from './actionTypes'

export const changeInpuValueAction = (value) => (
    {
        type:CHANGE_INPUT_VALUE,
        value
    }
)

export const addListItemAaction = function () {
    return {
        type:ADD_LIST_ITEM
    }
}

export const deleteListItemAction = function (index){
    return {
        type:DELETE_LIST_ITEM,
        index
    }
}

handleChange(e){
      const action = changeInpuValueAction(e.target.value)
       store.dispatch(action)
    }

    handleClick(){
        const action = addListItemAaction()
        store.dispatch(action)
    }

    deleteListItem(index){
        console.log(index)
        const action = deleteListItemAction(index)
        store.dispatch(action)
    }

Redux设计和使用的三项原则

  • store必须是唯一的
  • 只有store能够改变自己的内容
  • Reducer必须是纯函数
    纯函数指的是 给定固定的输入 就一定有固定的输出 而且不会有副作用

核心api
createStore 创建store
store.dispatch 派发action
store.getState 获取store中的内容
store.subscribe 订阅store的改变

UI组件与容器组件的拆分

渲染 逻辑拆分
创建TodoListUI.js组件(UI组件)

import React,{Component} from 'react'
import {Button, Input, List} from "antd";

class TodoListUI extends Component{
    render() {
        return(
            <div style={{marginLeft:'20px',marginTop:'20px'}}>
                <div>
                    <Input
                        value={this.props.value}
                        placeholder='antd design'
                        style={{width:'300px',marginRight:'20px'}}
                        onChange={this.props.handleChange}
                    />
                    <Button type="primary" onClick={this.props.handleClick}>提交</Button>
                </div>
                <List
                    style={{width:'300px',marginTop:'20px'}}
                    bordered
                    dataSource={this.props.list}
                    renderItem={
                        (item,index) => <List.Item  onClick={(index) =>{this.props.deleteListItem(index)}}>{item}</List.Item>
                    }
                />
            </div>
        )
    }
}

export default TodoListUI

TodoList.js修改(容器组件)

render() {
        return (
            <TodoListUI
                value={this.state.inputValue}
                handleChange={this.handleChange}
                handleClick={this.handleClick}
                deleteListItem={this.deleteListItem}
                list={this.state.list}
            />

        )
    }

组件之间的传值通过props传递属性

无状态组件

性能比较高

const TodoListUI = (props) =>{
    return(
        <div style={{marginLeft:'20px',marginTop:'20px'}}>
            <div>
                <Input
                    value={props.value}
                    placeholder='antd design'
                    style={{width:'300px',marginRight:'20px'}}
                    onChange={props.handleChange}
                />
                <Button type="primary" onClick={props.handleClick}>提交</Button>
            </div>
            <List
                style={{width:'300px',marginTop:'20px'}}
                bordered
                dataSource={props.list}
                renderItem={
                    (item,index) => <List.Item  onClick={(index) =>{props.deleteListItem(index)}}>{item}</List.Item>
                }
            />
        </div>
    )
}

Redux中发送异步请求获取数据

axios请求放在componentDidMount钩子函数中

 componentDidMount() {
        axios.get(
            '/todolist.json'
        ).then((res)=>{

            const data =res.data
            const action = initListAction(data)
            store.dispatch(action)
            console.log(action)
        })

    }

配置一下跨域请求json 也可以放在本地public静态资源中


```javascript
 "proxy": "http://139.XXX.XXX.xxx:8000"

创建action

export const initListAction = function (data){
    return {
        type:INIT_LIST_ITEM,
        data
    }
}

传给reducer

  if(action.type === INIT_LIST_ITEM){
        const newState = JSON.parse(JSON.stringify(state))
        newState.list=action.data
        return newState
    }

使用Redux-thunk中间件实现ajax数据请求

Redux-thunk可以帮助我们将异步请求或者比较复杂的请求放到action中进行处理
安装 yarn add redux-thunk

componentDidMount() {
        const action = initTodoList()
        //这里执行store.dispatch(action) action是个函数
        store.dispatch(action)

        // axios.get(
        //     '/todolist.json'
        // ).then((res)=>{
        //
        //     const data =res.data
        //     const action = initListAction(data)
        //     store.dispatch(action)
        //     console.log(action)
        // })

    }
export const initTodoList = function () {
    //对应   //这里执行store.dispatch(action) action是个函数
    return (dispatch) =>{
        axios.get(
            '/todolist.json'
        ).then((res)=>{
            const data =res.data
            console.log(data)
            //走redux流程
            const action = initListAction(data)
            //这里的action是对象
            dispatch(action)
        })
    }
}

到底什么是Redux中间件

在这里插入图片描述
指的是action与store之间
其实实质是对dispatch的封装
传给dispatch (如果参数是对象直接传给store,如果参数是函数,则将函数执行 )

Redux-saga中间件使用

import createSagaMiddleware from 'redux-saga'
import mySaga from './sagas'
// create the saga middleware
const sagaMiddleware = createSagaMiddleware()

const store = createStore(
    reducer,
    // applyMiddleware(thunk),
    applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(mySaga)

创建sagas.js

import {  put, takeEvery } from 'redux-saga/effects'
import {GET_INIT_LIST} from './actionTypes'
import {initListAction} from './actionCreators'
import axios from "axios";
import store from "./index";
function* getInitList() {
    // axios.get(
    //     '/todolist.json'
    // ).then((res)=>{
    //
    //     const data =res.data
    //     const action = initListAction(data)
    //     store.dispatch(action)
    //     console.log(action)
    // })
    //generator函数中可以这样写
    //yield 等axios请求之后
    const res = yield axios.get('/todolist.json')
    const action  = initListAction(res.data)
    // 等action处理完成之后
    yield put(action)
}

// es6 generator函数
function* mySaga() {
    // 只要接收到GET_INIT_LIST类型的action的时候 就会执行getInitList方法
    yield takeEvery(GET_INIT_LIST, getInitList);
}

export default mySaga;

export const getTodoList = function () {
    return {
        type:GET_INIT_LIST
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值