redux的入门使用
简介
- 在网上查到的各种redux的资料要么是各种复制官方文档的, 要么是讲的不明所以的
- 经历了这种痛苦的折磨后, 自己总算用redux写出了一个todomvc
事先准备
- 创建项目
- 需要下载的插件
-
antd
ui框架
-
babel-plugin-import
模块引入插件
-
babel-plugin-transform-decorators-legacy
redux的装饰器
-
react-redux 和 redux
这个不用说了
-
react-router-dom
react的路由
-
redux-thunk
处理异步的redux
-
node-sass 和 sass-loader
sass的插件
- 配置create-react-app
// 先安装好所有默认依赖包
yarn install
// 开启create-react-app的配置
yarn eject
// 在package.json中增加redux的装饰器配置 => 在babel的配置中添加
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
]
// 配置antd => 在config文件夹下的webpack.config文件的babel-loader的options里添加
"plugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css" // `style: true` 会加载 less 文件
}]
]
// 配置sass => 在config文件夹下的webpack.config文件的css-loader修改成
{
test: /\.(css|scss|sass)$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
"sass-loader",
{options.....不用修改}
}
}
项目结构
```js
|-- src
|-- component // 可复用的组件
|-- footer.js // 底部
|-- header.js // 头部
|-- main.js // 中间主体
|-- container // 骨架组件
|-- all.js // `/all`路由下对应的显示的组件
|-- active.js // `/active`路由下对应的显示的组件
|-- completed.js // `/completed`路由下对应的显示的组件
|-- store // 所有的状态管理
|-- actions // 所有的action文件夹
|-- action.js // 项目用到的action, 如果项目很大, 可以是多个action文件
|-- reducers // 所有的reducer文件夹
|-- add_reducer.js // 项目所用到的reducer, 如果项目更大, 可以是多个
|-- reducer.js // 组合所有的reducer
|-- types.js // 定义变量, 统一管理
|-- App.js // 项目主骨架
|-- app.scss // 项目的公共样式
|-- index.js // 把项目部署到页面
```
开始项目
- index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import {createStore, applyMiddleware, compose} from 'redux'
import reducers from './store/reducer'
// 将所有的reducer注册到页面
const store = createStore(reducers, compose(
applyMiddleware(thunk), // 使用thunk中间件
window.devToolsExtension ? window.devToolsExtension() : f => f // 这个是使用调试redux的插件
))
ReactDOM.render(
, document.getElementById('root')
)
- App.js
import React, { Component } from 'react'
import { Layout } from 'antd'
import Main from './component/main'
// 引入样式文件
import './app.scss'
class App extends Component {
render() {
const { Header, Footer, Content } = Layout
return (
)
}
}
export default App
- main.js
import React, { Component } from 'react'
import {BrowserRouter, Route, Switch} from 'react-router-dom'
import All from '../container/all'
import Active from '../container/active'
import Completed from '../container/completed'
import MainHeader from './header'
class Main extends Component{
render() {
return (
)
}
}
export default Main
- types.js
export const Add_TODO = 'ADD' // 添加
export const COMPLET_TODO = 'COMPLETED' // 完成单条
export const COMPLET_ALL_TODO = 'COMPLET_ALL_TODO' // 完成所有
export const CLEAR_TODO = 'CLEAR' // 清空所有完成的
export const DELETE_TODO = 'DELETE_TODO' // 删除单条
- reduer.js
import {combineReducers} from 'redux'
import {add} from './reducers/add_reducer'
export default combineReducers({add}) // 如果有多个, 只要放在add后面即可组合
- add_reducer.js
// 这里面的逻辑不是一开始就能写好的, 是随着项目的开发而慢慢完善的
import { Add_TODO, COMPLET_TODO, COMPLET_ALL_TODO, CLEAR_TODO, DELETE_TODO } from '../types'
// 定义初始的值
const initState = [
{
id: 0,
value: '',
checked: false
}
]
// 输出的reducer
export function add(state = initState, action){
switch (action.type){
// 增加操作
case Add_TODO:
return [
...state,
{
id: state.reduce((maxId, todo) => Math.max(maxId, todo.id), 0) + 1, // 此处给每条添加的数据增加id
value: action.payload.value,
checked: action.payload.checked
}
]
// 选中完成
case COMPLET_TODO:
state.map(v => {
if(v.id === action.payload.id){
v.checked = !v.checked
}
return v
})
return [...state]
// 全选
case COMPLET_ALL_TODO:
let flag = true
flag = state.every(v => v.checked === true)
state.map(v => {
flag ? v.checked = false : v.checked = true
return v
})
return [...state]
// 清除所有
case CLEAR_TODO:
state = initState
return [...state]
// 删除单条
case DELETE_TODO:
return [...(state.filter(v => v.id !== action.payload.id))]
default:
return state
}
}
- action.js
import { Add_TODO, COMPLET_TODO, COMPLET_ALL_TODO, CLEAR_TODO, DELETE_TODO } from '../types'
// 增加
export function addData(value){
return {type: Add_TODO, payload: {value, checked: false} }
}
// 选中
export function completeData(id){
return {type: COMPLET_TODO, payload: {id}}
}
// 全选
export function completeAllData(){
return {type: COMPLET_ALL_TODO}
}
// 清除
export function clearAll(){
return {type: CLEAR_TODO}
}
// 删除
export function deleteData(id){
return {type: DELETE_TODO, payload: {id}}
}
- header.js
import React, { Component } from 'react'
import { Form, Input, Button } from 'antd'
import { connect } from 'react-redux'
import { addData, completeAllData } from '../redux/actions/action' // 引入需要的action
// 装饰器的写法, 将action和state注册到组件
@connect(
state => ({add: state.add}),
{ addData, completeAllData }
)
class MainHeader extends Component{
constructor(props){
super(props)
this.completedAll = this.completedAll.bind(this)
}
completedAll(){
if(this.props.add.length < 1) return
this.props.completeAllData()
}
handleEnter(v){
const value = v.target.value
this.props.addData(value)
v.target.value = ' '
}
render() {
const FormItem = Form.Item
return(
)
}
}
export default MainHeader
- footer.js
import React, { Component } from 'react'
import {Link} from 'react-router-dom'
import { Form } from 'antd'
import { clearAll } from '../redux/actions/action'
import { connect } from 'react-redux'
@connect(
state => ({add: state.add.slice(1)}),
{ clearAll }
)
class MainFooter extends Component{
render() {
const FormItem = Form.Item
let left = 0
this.props.add.forEach(v => {
if(v.checked === false){
left += 1
}
})
return(
)
}
}
export default MainFooter
- all.js
import React, { Component } from 'react'
import MainFooter from '../component/footer'
import { List, Checkbox, Icon } from 'antd'
import { connect } from 'react-redux'
import { completeData, deleteData } from '../redux/actions/action'
@connect(
state => ({add: state.add}),
{completeData, deleteData}
)
class All extends Component{
constructor(props){
super(props)
this.state = {
showClose: false,
itemId: 0
}
this.handleCheck = this.handleCheck.bind(this)
}
handleCheck(id){
this.props.completeData(id)
}
render() {
// 去除第一个默认的数据
const allData = this.props.add.slice(1)
return (
{allData.length < 1 ? null : allData.map(v => {
if(v.id === 0) return null
return (
this.setState({showClose:true,itemId: v.id})} onMouseLeave={() => this.setState({showClose: false})}>
{this.state.showClose && this.state.itemId === v.id ? this.props.deleteData(v.id) }> : ''}
this.handleCheck(v.id)}>
)
})}
)
}
}
export default All
- active.js
import React, { Component } from 'react'
import MainFooter from '../component/footer'
import { List, Checkbox, Icon } from 'antd'
import { connect } from 'react-redux'
import { completeData, deleteData } from '../redux/actions/action'
@connect(
state => ({add: state.add}),
{completeData, deleteData}
)
class Active extends Component{
constructor(props){
super(props)
this.handleCheck = this.handleCheck.bind(this)
}
handleCheck(id){
this.props.completeData(id)
}
render() {
// 获取未选中的数据
const allData = this.props.add.slice(1).filter(v => v.checked === false)
return (
{allData.length < 1 ? null : allData.map(v => {
if(v.id === 0) return null
return (
this.props.deleteData(v.id) }>
this.handleCheck(v.id)}>
)
})}
)
}
}
export default Active
- completed.js
import React, { Component } from 'react'
import MainFooter from '../component/footer'
import { List, Checkbox, Icon } from 'antd'
import { connect } from 'react-redux'
import { completeData, deleteData } from '../redux/actions/action'
@connect(
state => ({add: state.add}),
{completeData, deleteData}
)
class Completed extends Component{
constructor(props){
super(props)
this.handleCheck = this.handleCheck.bind(this)
}
handleCheck(id){
this.props.completeData(id)
}
render() {
// 获取选中的数据
const allData = this.props.add.slice(1).filter(v => v.checked === true)
return (
{allData.length < 1 ? null : allData.map(v => {
if(v.id === 0) return null
return (
this.props.deleteData(v.id) }>
this.handleCheck(v.id)}>
)
})}
)
}
}
export default Completed
html, body, #root{
width: 100%;
height: 100%;
}
.App{
width: 100%;
height: 100%;
.ant-layout{
width: 100%;
height: 100%;
.ant-layout-header{
h2{
color: #fff;
text-align: center;
height: 100%;
font-size: 35px;
margin: 0;
}
}
.footer{
width: 100%;
text-align: center;
display: block;
color: #999;
}
.ant-layout-content{
height: 80%;
overflow: auto;
.container{
width: 40%;
height: 50%;
margin: 100px auto;
.ant-list-item{
overflow: hidden;
position: relative;
.ant-list-item-meta{
width: 100%;
.ant-list-item-meta-content{
width: 100%;
.ant-list-item-meta-description{
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.ant-list-item-content{
position: absolute;
top: 12px;
left: 3px;
width: 100%;
i.anticon{
top: 5px;
right: 12px;
position: absolute;
cursor: pointer;
}
.ant-checkbox-wrapper{
position: absolute;
top: 0;
left: 0;
}
}
}
}
}
.form-footer{
.footer-left{
float: left;
}
.footer-link{
float: left;
margin-left: 60px;
a{
margin-left: 20px;
display: inline-block;
color: #777;
}
}
.footer-right{
float: right;
cursor: pointer;
}
}
}
}