React全家桶

1.redux
2.react-redux
3.react-router
安装redux

	npm install redux --save

redux上手
创建store,src/store.js

	import {createStore} from 'redux'
	const counterReducer = (state = 0, action) => {
    switch (action.type) {
      case 'add':
        return state + 1
      case 'minus':
        return state - 1
      default:
        return state
    }
  }
const store = createStore(counterReducer)
 
export default store

创建ReduxTest.js,components/ReduxTest.js

	import React, { Component } from "react";
import store from "../store";
 
export default class ReduxTest extends Component {
  render() {
    return (
    <div>
        <p>{store.getState()}</p>
        <div>
          <button onClick={() => store.dispatch({ type: "add" })}>+</button>
          <button onClick={() => store.dispatch({ type: "minus" })}>-</button>
        </div>
      </div>
    );
  }
}

订阅状态变更,index.js

	import store from './store'
	const render = ()=>{
	 
	  ReactDom.render(
	    <App/>,
	    document.querySelector('#root')
	  )
	}
	render()
	 
	store.subscribe(render)

react-redux

将redux整合到react中,需要react-redux的支持

	npm install react-redux --save

全局提供store,index.js

	import React from 'react'
	import ReactDom from 'react-dom'
	import App from './App'
	import store from './store'
	 
	import { Provider } from 'react-redux'
	ReactDom.render(
	  <Provider store={store}>
	    <App/>
	  </Provider>,
	  document.querySelector('#root')
	)

获取状态数据,ReduxTest.js

	// import store from "../store";
import { connect } from "react-redux";
 
@connect(
  state => ({ num: state }), // 状态映射
  {
    add: () => ({ type: "add" }), // action creator
    minus: () => ({ type: "minus" }) // action creator
  }
)
class ReduxTest extends Component {
  render() {
    return (
      <div>
        <p>{this.props.num}</p>
        <div>
          <button onClick={this.props.add}>+</button>
          <button onClick={this.props.minus}>-</button>
        </div>
      </div>
    );
  }
}
export default ReduxTest;

异步操作
react默认只支持同步,实现异步任务需要中间件的支持

npm install redux-thunk redux-logger --save

应用中间件,store.js

	import { createStore, applyMiddleware } from "redux";
	import logger from "redux-logger";
	import thunk from "redux-thunk";
	 
	const store = createStore(fruitReducer, applyMiddleware(logger, thunk));

使用异步操作时的变化,ReduxTest.js

	@connect(
	  state => ({ num: state }),
	  {
	    ...,
	    asyncAdd: () => dispatch => {
	      setTimeout(() => {
	        // 异步结束后,手动执行dispatch
	        dispatch({ type: "add" });
	      }, 1000);
	    }
	  }
	)

代码优化
抽离reducer和action,创建store/counter.js

	export const counterReducer = (state = 0, action) => {};
 
	export const add = num => ({ type: "add", payload: num });
	export const minus = num => ({ type: "minus", payload: num });
	export const asyncAdd = num => dispatch => {};

移动action.js并重命名为index.js

	import {counterReducer} from './counter'
	const store = createStore(counterReducer, applyMiddleware(logger, thunk));
	export default store;

ReduxTest.js

	import { add, minus, asyncAdd } from "../store/counter";
	@connect(
	  state => ({ num: state }),
	  { add, minus, asyncAdd }
	)

模块化

store/index.js

	import { combineReducers } from "redux";
 
	const store = createStore(
	combineReducers({counter: counterReducer}), 
	applyMiddleware(logger, thunk)
	);

ReduxTest.js

	@connect(
	state => ({ num: state.counter }), // 添加一个counter
	{ add, minus, asyncAdd }
	)

redux原理

核心功能实现
store/redux.js

	export function createStore(reducer, enhancer){
    if (enhancer) {
        return enhancer(createStore)(reducer)
    }
    let currentState = undefined;
    let currentListeners = [];
 
    function getState(){
        return currentState
    }
    function subscribe(listener){
        currentListeners.push(listener)
    }
    function dispatch(action){
        currentState = reducer(currentState, action)
        currentListeners.forEach(v=>v())
        return action
    }
    dispatch({type:'@IMOOC/KKB-REDUX'})
    return { getState, subscribe, dispatch}
}

测试代码,components/MyReduxTest.js

	import React, { Component } from "react";
	import { createStore } from "../store/redux";
	 
	const counterReducer = (state = 0, action) => {
	  switch (action.type) {
	  case "add":
	      return state + 1;
	    case "minus":
	      return state - 1;
	    default:
	      return state;
	  }
	};
	 
	const store = createStore(counterReducer);
	 
	export default class MyReduxTest extends Component {
	 
	  componentDidMount() {
	    store.subscribe(() => this.forceUpdate());
	  }
	 
	  render() {
	    return (
	      <div>
	        {store.getState()}
	        <button onClick={() => store.dispatch({ type: "add" })}>+</button>
	      </div>
	    );
	  }
	}

中间件实现

核心任务是实现函数序列执行,redux.js

	export function applyMiddleware(...middlewares){
    return createStore => (...args) => {
        const store = createStore(...args)
        let dispatch = store.dispatch
 
        const midApi = {
            getState:store.getState,
            dispatch:(...args)=>dispatch(...args)
        }
        const middlewareChain = middlewares.map(middleware => middleware(midApi))
        dispatch = compose(...middlewareChain)(store.dispatch)
        return {
            ...store,
            dispatch
        }
    }
}
export function compose(...funcs){
    if (funcs.length==0) {
        return arg=>arg
    }
    if (funcs.length==1) {
    return funcs[0]
    }
    return funcs.reduce((left,right) => (...args) => right(left(...args)))
}

测试代码,MyReduxTest.js

	import { applyMiddleware } from "../store/redux";
 
	function logger({dispatch, getState}) {
	    return dispatch => action => {
	        // 中间件任务
	        console.log(action.type + '执行了!!');
	        // 下一个中间件
	        return dispatch(action);
	    }
	}
	 
	const store = createStore(counterReducer, applyMiddleware(logger));

redux-thunk原理

	const thunk = ({dispatch, getState}) => dispatch => action => {
    if (typeof action == 'function') {
        return action(dispatch, getState)
    }
    return dispatch(action)
}
	export default thunk

测试代码

		const store = createStore(counterReducer, applyMiddleware(logger,thunk));
	 
	<button onClick={() => store.dispatch(() => {
	    setTimeout(() => {
	        store.dispatch({ type: "add" })
	    }, 1000);
	})}>+</button>

react-redux原理 实现react-redux

核心任务:属性映射、变更检测和刷新;实现一个Provider组件可以传递store

	import React from 'react'
	import PropTypes from 'prop-types'
import {bindActionCreators} from './kkb-redux'
 
export const connect = (mapStateToProps = state=>state, mapDispatchToProps = {}) =>
(WrapComponent)=>{
    return class ConnectComponent extends React.Component{
        static contextTypes = {
            store: PropTypes.object
        }
        constructor(props, context){
            super(props, context)
            this.state = {
                props:{}
            }
        }
        componentDidMount(){
            const {store} = this.context
            store.subscribe(()=>this.update())
            this.update()
        }
        update(){
            const {store} = this.context
            const stateProps = mapStateToProps(store.getState())
            const dispatchProps = bindActionCreators(mapDispatchToProps, 
store.dispatch)
            this.setState({
                props:{
                    ...this.state.props,
                    ...stateProps,
                    ...dispatchProps    
                }
            })
        }
        render(){
            return <WrapComponent {...this.state.props}></WrapComponent>
        }
    }
}
 
export class Provider extends React.Component{
    static childContextTypes = {
        store: PropTypes.object
    }
    getChildContext() {
        return { store: this.store }
    }
    constructor(props, context) {
        super(props, context)
        this.store = props.store
    }
    render() {
        return this.props.children
    }
    }

实现bindActionCreators
添加一个bindActionCreators能转换actionCreator为派发函数,redux.js

	function bindActionCreator(creator, dispatch){
    return (...args) => dispatch(creator(...args))
	}
	export function bindActionCreators(creators,dispatch){
	    return Object.keys(creators).reduce((ret,item)=>{
	        ret[item] = bindActionCreator(creators[item],dispatch)
	        return ret
	    },{})
	}

react-router-4

安装: npm install --save react-router-dom
基本使用:
范例:把水果市场转换为多页面,ReduxTest.js

	import { BrowserRouter, Link, Route } from "react-router-dom";
 
	function ReduxTestContainer({}) {
	  return (
	    {/* 顶层添加BrowserRouter */}
	    <BrowserRouter>
	      {/* 导航的使用 */}
	      <nav>
	        <Link to="/">水果列表</Link>| 
	        <Link to="/add">添加水果</Link>
	      </nav>
	      <div>
	        {/* 根路由要添加exact,render可以实现条件渲染 */}
	        <Route
	          exact
	          path="/"
	          render={props =>
	            loading ? <div>数据加载中...</div> : <FruitList fruits={fruits} />
	          }
	        />
	        <Route path="/add" component={FruitAdd} />
	      </div>
	    </BrowserRouter>
	  );
	}

注意render和component有竞争关系,component优先于render,二选一即可,原因在这里
我们还发现了children选项(不管路由是否匹配都会渲染)

路由参数

和vue一样,试用:id的形式定义参数
添加导航链接,FruitList

	<Link to={`/detail/${f}`}>{f}</Link>

定义路由,ReduxTest

	<Route path="/detail/:fruit" component={Detail} />

创建Detail并获取参数

	function Detail({ match, history, location }) {
	  console.log(match, history, location);
	  return (
	    <div>
	      <h3>{match.params.fruit}的详细信息</h3>
	      <p>...</p>
	      <div>
	        <button onClick={history.goBack}>返回</button>
	      </div>
	    </div>
	  );
	}

顺带看一下history和location

嵌套

Route组件嵌套在其他页面组件中就产生了嵌套关系
去掉根路由的exact,避免Detail不可见

	<Link to="/list">水果列表</Link>
 
	<Route
	  path="/list"
	  render={props =>
	    loading ? <div>数据加载中...</div> : <FruitList fruits={fruits} />
	  }
	/>

还可以加个重定向默认跳转

	<Redirect to="/list"></Redirect>

移动Detail相关路由到FruitList

	<ul>
    {fruits.map(f => (
        <li key={f} onClick={() => setFruit(f)}>
            {/*地址相对于父路由*/}
            <Link to={`/list/detail/${f}`}>{f}</Link>
        </li>
    ))}
</ul>
{/*地址相对于父路由*/}
<Route path="/list/detail/:fruit" component={Detail} />

404页面

	{/* 添加Switch表示仅匹配一个 */}
<Switch>
    {/* 首页重定向换成Route方式处理避免影响404 */}
    <Route exact path="/" render={props => <Redirect to="/list" />} />
    {/* <Redirect to="/list"></Redirect> */}
    <Route component={() => <h3>页面不存在</h3>}></Route>
</Switch>

路由守卫

思路:创建高阶组件包装Route使其具有权限判断功能
创建PrivateRoute

	function PrivateRoute({ component: Component, isLogin, ...rest }) {
	  // 结构props为component和rest
	  // rest为传递给Route的属性
	  return (
	    <Route
	      {...rest}
	      render={
	        // 执行登录判断逻辑从而动态生成组件
	        props =>
	          isLogin ? (
	            <Component {...props} />
	          ) : (
	          <Redirect
	              to={{
	                pathname: "/login",
	                state: { redirect: props.location.pathname } // 重定向地址
	              }}
	            />
	          )
	      }
	    />
	  );
	}

创建Login

	function Login({ location, isLogin, login }) {
  const redirect = location.state.redirect || "/"; // 重定向地址
 
  if (isLogin) return <Redirect to={redirect} />;
 
  return (
    <div>
      <p>用户登录</p>
      <hr />
      <button onClick={login}>登录</button>
    </div>
  );
}

配置路由,ReduxTest

	<PrivateRoute path="/add" component={FruitAdd} />
	<Route path="/login" component={Login} />

整合redux,获取和设置登录态,创建./store/user.redux.js

	const initialState = { isLogin: false, loading: false };
export default (state = initialState, action) => {
  switch (action.type) {
    case "requestLogin":
      return { isLogin: false, loading: true };
    case "loginSuccess":
      return { isLogin: true, loading: false };
    case "loginFailure":
      return { isLogin: false, loading: false };
    default:
      return state;
  }
};
export function login(user) {
  return dispatch => {
    dispatch({ type: "requestLogin" });
    setTimeout(() => {
      if (Date.now() % 2 === 0) {
        dispatch({ type: "loginSuccess" });
      } else {
        dispatch({ type: "loginFailure" });
      }
    }, 1000);
  };
}

引入,store/index.js

	import user from "./user.redux";
 
	const store = createStore(
	  combineReducers({ fruit: fruitReducer, user }),
	  applyMiddleware(logger, thunk)
	);

连接状态,ReduxTest.js

	import { login } from "./store/user.redux";
 
	const PrivateRoute = connect(state => ({
	  isLogin: state.user.isLogin
	}))(function({ component: Component, isLogin, ...rest }) {})
	 
	const Login = connect(
	  state => ({
	    isLogin: state.user.isLogin
	  }),
	  { login }
	)(function({ location, isLogin, login }) {})

react-router实现

**BrowserRouter:**历史记录管理对象history初始化及向下传递,location变更监听

	import { createBrowserHistory } from "history";
	const RouterContext = React.createContext();
 
class BrowserRouter extends Component {
  constructor(props) {
    super(props);
 
    this.history = createBrowserHistory(this.props);
 
    this.state = {
      location: this.history.location
    };
 
    this.unlisten = this.history.listen(location => {
      this.setState({ location });
    });
  }
 
  componentWillUnmount() {
    if (this.unlisten) this.unlisten();
  }
 
  render() {
    return (
      <RouterContext.Provider
        children={this.props.children || null}
        value={{
          history: this.history,
          location: this.state.location
        }}
      />
    );
  }
}

Route:路由配置,匹配检测,内容渲染

	import matchPath from "./matchPath";
 
export default class Route extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const location = context.location;
          const match = matchPath(location.pathname, this.props)
          
          // 要传递给下去的属性
          const props = { ...context, match };
 
          let { children, component, render } = this.props;
 
          if (children && typeof children === "function") {
            children = children(props);
             }
 
          return (
            <RouterContext.Provider value={props}>
              {children && React.Children.count(children) > 0
                ? children
                : props.match
                ? component
                  ? React.createElement(component, props)
                  : render
                  ? render(props)
                  : null
                : null}
            </RouterContext.Provider>
          );
        }}
      </RouterContext.Consumer>
    );
  }
}

matchPath.js

	import pathToRegexp from "path-to-regexp";
 
const cache = {};
const cacheLimit = 10000;
let cacheCount = 0;
 
// 转换path为正则和关键字数组
function compilePath(path, options) {
    const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
    const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
 
    if (pathCache[path]) return pathCache[path];
 
    const keys = [];
    const regexp = pathToRegexp(path, keys, options);
    const result = { regexp, keys };
 
    if (cacheCount < cacheLimit) {
     pathCache[path] = result;
     cacheCount++;
    }
 
    return result;
}
 
/**
 * 匹配pathname和path.
*/
function matchPath(pathname, options = {}) {
    if (typeof options === "string") options = { path: options };
    const { path, exact = false, strict = false, sensitive = false } = options;
 
    const paths = [].concat(path);
    // 转换path为match
    return paths.reduce((matched, path) => {
     if (!path) return null;
     if (matched) return matched;
     // 转换path为正则和占位符数组
     const { regexp, keys } = compilePath(path, {
       end: exact,
       strict,
       sensitive
     });
     // 获得正则匹配数组
     const match = regexp.exec(pathname);
 
     if (!match) return null;
 
     // 结构出匹配url和值数组
     const [url, ...values] = match;
     const isExact = pathname === url;
 
     if (exact && !isExact) return null;
 
     return {
       path, // 待匹配path
       url: path === "/" && url === "" ? "/" : url, // url匹配部分
       isExact, // 精确匹配
       params: keys.reduce((memo, key, index) => { // 参数
         memo[key.name] = values[index];
         return memo;
       }, {})
     };
    }, null);
}
 
export default matchPath;

Link.js: 跳转链接,处理点击事件

	class Link extends React.Component {
  handleClick(event, history) {
    event.preventDefault();
    history.push(this.props.to);
  }
 
  render() {
    const { to, ...rest } = this.props; 
 
    return (
    <RouterContext.Consumer>
        {context => {
          return (
            <a
              {...rest}
              onClick={event => this.handleClick(event, context.history)}
              href={to}
            >
              {this.props.children}
            </a>
          );
        }}
      </RouterContext.Consumer>
    );
  }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值