react、react-router、redux 也许是最佳小实践2

8

上一篇:react、react-router、redux 也许是最佳小实践1

加入 redux

React 在组件之间流通数据.更确切的说,这被叫做“单向数据流”——数据沿着一个方向从父组件流到子组件。由于这个特性,对于没有父子关系的两个组件之间的数据交流就变得不是那么显而易见。这里 Redux 就排上用场了。Redux提供了一个解决方案,通过将应用程序所有的状态都存储在一个地方,叫做“store”。然后组件就可以“dispatch”状态的改变给这个store,而不是直接跟另外的组件交流。所有的组件都应该意识到状态的改变可以“subscribe”给store。如下图:
Alt text

原理讲完,下面开始加入代码。
先看看一个小例子。

开始之前,需要先用 Redux.createStore() 创建一个store,然后将所有的reducer作为参数传递进去,让我们看一下这个只传递了一个reducer的小例子:

var userReducer = function(state, action) {
  if (state === undefined) {
    state = [];
  }
  if (action.type === 'ADD_USER') {
    state.push(action.user);
  }
  return state;
}

var store = Redux.createStore(userReducer);

store.dispatch({
  type: 'ADD_USER',
  user: {name: 'xiaoming'}
});

上面的程序干了些什么呢:

  1. 这个store只由一个reducer创建。

  2. 这个reducer 初始化状态的时候使用了一个空数组 。*

  3. 在被分派的这个action里面使用了新的user对象。

  4. 这个reducer将这个新的user对象附加到state上,并将它返回,用来更新store。

*在这个例子里reducer实际上被调用了两次 —— 一次是在创建store的时候,一次是在分派action之后。

当store被创建之后,Redux立即调用了所有的reducer,并且将它们的返回值作为初始状态。第一次调用reducer传递了一个 undefined 给state。经过reducer内部的代码处理之后返回了一个空数组给这个store的state作为开始。

所有的reducer在每次action被分派之后都会被调用。因为reducer返回的状态将会成为新的状态存储在store中,所以 Redux总是希望所有的reducer都要返回一个状态。

在这个例子中,reducer第二次的调用发生在分派之后。记住,一个被分派的action描述了一个改变状态的意图,而且通常携带有数据用来更新状态。这一次,Redux将当前的状态(仍旧是空数组)和action对象一起传递给了reducer。这个action对象,现在有了一个值为1ADD_USERtype属性, 让reducer知道怎样改变状态。

正式redux登场

src 下面创建一个 reduxactionsdata(存放一些初始数据)文件夹,然后在 data文件夹下面创建一个db.js,这个文件写上一些初始的数据:

src/data/db.js

const data = [
    {
        id: 1,
        title: '明天要去打酱油',
        content: '系呀系呀我们一起打酱油'
    },
    {
        id: 2,
        title: '周末去书吧读书',
        content: '书籍是人类进步的阶梯'
    },
    {
        id: 3,
        title: '备份一下数据库',
        content: '备份服务器的数据库,一般都是分开的,分布式数据库'
    },
    {
        id: 4,
        title: '周五记得把被子洗了',
        content: '洗杯子被子被子被子'
    },
    {
        id: 5,
        title: '计划五',
        content: '计划五内容'
    }
]

export default data

好了,初始的数据我们有了,下面就是创建 store 了,在redux文件夹下面,创建一个planlist.js文件,这个文件就是操作 store 的动作 action集合处理的数据,这时候我们会去action文件夹下面新建,action-type.jsplan.js,代码如下:

src/action/action-type.js

export const ADD = 'ADD';
export const DELECT = 'DELECT';
export const SHOW = 'SHOW';

src/action/plan.js

import * as types from './action-type.js';
// 添加计划
export function addPlan(item) {
  return {
    type: types.ADD,
    item
  };
}
// 删除计划
export function deletePlan(id) {
  return {
    type: types.DELECT,
    id
  };
}
// 显示隐藏弹层
export function show(show) {
  return {
    type: types.SHOW,
    show
  };
}

action 我们都定义好了现在我们就可以改变 store了。写好我们的 reducer

src/redux/planlist.js

import * as types from '../actions/action-type.js';
import data from '../data/db.js'
const initialState = {
  show: false, // 是否显示弹出
  planlist: data // 初始的计划表
};

const planReducer = function(state = initialState, action) {
    let list = state.planlist;
  switch(action.type) {
    // 添加计划
    case types.ADD:
        list.push(action.item);
      return Object.assign({}, state, { planlist: list });
    // 删除计划
    case types.DELECT:
      let newstate = list.filter((item) => item.id != action.id);
      return Object.assign({}, state, { planlist: newstate });;
     // 显示、隐藏弹出层
     case types.SHOW:
         return Object.assign({}, state, { show: action.show });
  }
  return state;

}

export default planReducer;

在redux 下面再创建reducers.jsstore.js

src/redux/reducers.js

import { combineReducers } from 'redux';

// Reducers
import planlist from './planlist';

// Combine Reducers
var reducers = combineReducers({
    planlist: planlist
});

export default reducers;

src/redux/store.js

import { createStore } from 'redux';
import reducers from './reducers.js';

const store = createStore(reducers);
export default store;

这会我们的 store 就完全的创建好了,下面就是把 store 跟我们的组件,完全的结合起来。这就用到 react-redux 的 connect 模块。
这个东西 就是把组件跟 store 连接起来的模块。

然后在,App.js加入我们的。store

src/App.js

import React, { Component } from 'react'
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom'
// 引入 store
import { Provider, connect } from 'react-redux';
import store from './redux/store.js'
import logo from './logo.svg'
import Plan from './components/plan.js'
import Home from './components/home.js'
import Popup from './components/pupop.js'
import TestRouter from './components/testrouter.js'
import Detail from './components/detail.js'
import './App.css'
import './components/comment.css'
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
class App extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        // store的挂载
       <Provider store={store}>
        <div className="App">
            <div className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <h2 className='App-title'>Welcome to React Plan</h2>
            </div>
            <div>
              <Router history = {history}>
                 <div className="contentBox">
                    <ul className="nav">
                      <li><Link to="/">首页</Link></li>
                      <li><Link to="/plan">计划表</Link></li>
                      <li><Link to="/test">二级路由</Link></li>
                    </ul>
                    <div className="content"> 
                      <Route exact path="/" component={Home}/>
                      <Route path="/plan" component={Plan}/>
                      <Route path="/test" component={TestRouter}/>
                      <Route path="/detail/:id" component={Detail}/>
                    </div>
                </div>
              </Router>
            </div>
            <Popup/>
        </div>
      </Provider>
    );
  }
}

export default App

然后在 plan.js连接 store

src/component/plant.js

import React, { Component } from 'react'
import { connect } from 'react-redux';
import store from '../redux/store.js';
// 引入 定义的 action
import {show, deletePlan} from '../actions/plan.js';

class Plan extends Component {
  constructor(props) {
      super(props);
  }
  // 显示弹出
  show () {
    let b = this.props.planlist.show;
    store.dispatch(show(!b));
  }
  // 删除计划
  delete (id) {
      store.dispatch(deletePlan(id));
  }
  // js 跳转路由
  detail (id) {
      this.props.history.push(`/detail/${id}`)
  }
    render () {
        return (
            <div>
                <div className="plant">
                    <h3>计划表</h3>
                    <p onClick={this.show.bind(this)}>添加计划</p>
                </div>
                <table className="planlist">
                    <thead>
                        <tr>
                            <th>标题</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody>
                        {
                            this.props.planlist.planlist.map((item, index) => {
                                return (
                                    <tr key={index}>
                                        <td className="plan-title" onClick={this.detail.bind(this, item.id)}>{item.title}</td>
                                        <td className="plan-delect" onClick={this.delete.bind(this, item.id)}>删除</td>
                                    </tr>
                                )
                            })
                        }
                    </tbody>
                </table>
            </div>
        )
    }
}

const mapStateToProps = function(store) {
  return {
    planlist: store.planlist
  };
};
// 连接 store,作为 props
export default connect(mapStateToProps)(Plan);

同理下面的 js,都是用这个模块连接

src/component/detail.js

import React, { Component } from 'react'
import { connect } from 'react-redux';
import store from '../redux/store.js';


class Detail extends Component {
    constructor(props) {
        super(props);
        // 根据路由 id 跟 store 做过滤
        let item = props.planlist.planlist.filter((data) => data.id == props.match.params.id)
        console.log(item)
        this.state = {
            plan: item[0]
        }
    }
    render() {
        return (
            <div style={{padding: '20px'}}>
                <h3>计划详情</h3>
                <p>id: {this.state.plan.id}</p>
                <p>标题: {this.state.plan.title}</p>
                <p>内容: {this.state.plan.content}</p>
            </div>

        )
    }
}


const mapStateToProps = function(store) {
  return {
    planlist: store.planlist
  };
};
// 连接 tore 和组件
export default connect(mapStateToProps)(Detail);

src/component/popup.js

import React, { Component } from 'react'
import { connect } from 'react-redux';
import store from '../redux/store.js';
import {show, addPlan} from '../actions/plan.js';

class Pupop extends Component{
  constructor (props) {
    super(props)
    this.state = {
      id: '',
      title: '1',
      content: '1'
    }
  }
  // 取消按钮操作
  close () {
    let b = this.props.planlist.show;
    this.setState({
      id: '',
      title: '',
      content: ''
    })
    store.dispatch(show(!b));
  }
  // 输入框事件
  handleChage (str, e) {
    this.setState({
      id: Math.ceil(Math.random()*10000),
      [str]: e.target.value
    })
  }
  // 确认操作
  conform () {
    store.dispatch(addPlan(this.state));
    this.setState({
      id: '',
      title: '',
      content: ''
    })
    this.close();
  }

  render() {
    let self = this;
    return (
      <section className="popup" style={this.props.planlist.show ? {} : {display: 'none'}}>
        <div className="pbox">
          <span className="close" onClick={this.close.bind(this)}>X</span>
          <div>
            <h4>计划标题</h4>
            <input onChange={this.handleChage.bind(this, 'title')} value={this.state.title} placeholder="请输入计划标题"/>
          </div>
          <div>
            <h4>计划内容</h4>
            <textarea onChange={this.handleChage.bind(this, 'content')} value={this.state.content} placeholder="请输入计划内容" rows="3"></textarea>
          </div>
          <div className="pBtn">
            <span onClick = {this.close.bind(this)}>取消</span>
            <span onClick = {this.conform.bind(this)}>确认</span>
          </div>
        </div>
      </section>
    )
  }
}

const mapStateToProps = function(store) {
  return {
    planlist: store.planlist
  };
};
// 连接 store和组件
export default connect(mapStateToProps)(Pupop);

完工。github地址

你可能感兴趣的

载入中...