React学习笔录二 + React Router+Redux-thunk+Redux-logger

1.组件化

react组件化跟vue有所区别,容器组件 VS 展示组件

状态的维护,数据获取,逻辑的处理尽量在容器组件里面

展示组件尽量是傻瓜式的,纯粹的页面展示的组件,输入props就能知道展示出来是啥样,不包含状态逻辑的处理

2. PureComponent

展示型组件可以配合PureComponent,不过要注意几点:

2-1. 改变引用的数据,PureComponent是没有效果的,比如直接替换整个list数组(可以使用 <Test {...data} /> 用...进行赋值,data是list的单个对象)

2-2. 数据的嵌套层特别多,这样用PureComment反而开销大,尽量要嵌套少,浅层次的

函数式组件,如果要用PureComponent,可以用React.memo进行嵌套

3.高阶组件(HOC)

高阶组件是一个函数,接受一个组件,返回一个新的组件,新的组件可以对属性进行封装,还能重写部分生命周期

高阶组件是可以嵌套的,可以配合装饰器使用 (Babel7)

// MyHigh.js

// 简单的示例,属性name要单独获取(比如从接口Api获取,这里做个简单示例name),进行扩展

// 返回一个组件,是个函数,参数是props

import React from 'react'

const MyHigh=(Com)=>{

    const name='自定义名字';

    return (props)=> <Com {...props} name={name} />
}

export default MyHigh

// App.js

import Foot from './components/Foot'
import MyHighCom from './components/MyHigh'

const NewFoot=MyHighCom(Foot);

render() {

    return (
      <div className="wrap">
        <NewFoot age={18} /> 
      </div>
    )
}

// Foot.js

const Footer = (props) => (
  <div>
    <span>Show: </span>
    <p>{props.name}----{props.age}</p>
  </div>
)

4.组件复合,扩展组件           props.children 就是一个合法的js表达式(JSX都是包含在内的)

// 类似 vue的 默认插槽, 用 props.children 展示

import React from 'react'

function Dialog(props){
    return <div style={{border:'3px solid green'}}>{props.children}</div>
}

const WelcomeDialog=(props)=>{
    return (
        <Dialog>
            <h1>欢迎光临</h1>
        </Dialog>
    )
}

export default WelcomeDialog
// 类似vue的具名插槽,就是利用属性传递,可以用在任何位置,下面只是footer举例

// Composition.js

import React from 'react'

function Dialog(props){
    return (
       <div style={{border:'3px solid green'}}>
           {props.children}
           {/*类似vue的具名插槽,但是这个属性名字footer要约定,说到底就是传递属性来实现*/}
           <div className="footer">{props.footer}</div>
       </div>
    ) 
}

const WelcomeDialog=(props)=>{
    return (
        <Dialog {...props}>
            <h1>欢迎光临</h1>
        </Dialog>
    )
}

export default WelcomeDialog

// 使用

<Composition footer={<button>确定</button>} />
// children是个函数

// 给Fetcher.children 的传入一个对象,在插槽里面使用,类似于vue的作用域插槽


import React from 'react'

const WelcomeDialog=(props)=>{
    return (
        <Fetcher name='getUser'>
            {
                ({name,age})=>(
                    <div>{name}---{age}</div>
                )
            }

        </Fetcher>

    )
}

const Api={
    getUser(){
        return {name:'张三',age:18}
    }
}

function Fetcher(props){
    const user= Api[props.name]();
    return props.children(user);
}

export default WelcomeDialog
// 父级组件传递属性,进行统一管理,利用props.children

import React from 'react'

function Radio({children,...rest}){
    return (
        <label htmlFor={rest.vaule}>
            <input id={rest.vaule} type="radio" {...rest} />
            {children}
        </label>
    )
}

function RadioGroup(props){
    return (
        <div>
            {
                React.Children.map(props.children,(child)=>{
                    // child是一个虚拟dom,不可直接添加修改属性,要克隆
                   return React.cloneElement(child,{name:props.name});
                })
            }
        </div>
    )
}

export default ()=>{
    return (
        <RadioGroup name="mvvm">
            <Radio value='vue'>vue</Radio>
            <Radio value='react'>react</Radio>
            <Radio value='angular'>angular</Radio>
        </RadioGroup>
    )
}

5. 实例实战  (扩展Form表单,增加验证消息展示)

import React, { Component } from "react";

// 包含在空间外层的,form的直接元素,控制布局和样式之类的,错误消息提示
// children直接展示嵌套的组件,用于扩展
function FormItem(props) {
  const { children, status, help } = props;
  return (
    <div>
      {children}
      {status === "error" && (
        <p style={{ color: "red", fontSize: "12px" }}>{help}</p>
      )}
    </div>
  );
}

// 高阶组件 扩展传入组件功能,返回新的组件
// 增加字段的验证,所有字段的全局验证,表单组件的装饰器[值和name由父级控制]
function MyFormCreate(Com) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = {};
      this.options = {};
    }

    // 输入框的change事件

    handleChange = e => {
      const { value, name } = e.target;

      this.setState(
        {
          [name]: value
        },
        () => {
          this.validateField(name); //用回调方式,确保值变了,进行验证
        }
      );
    };

    // 全局验证,扩展出去,给使用组件调用
    validateAllField = cb => {
      const result = Object.keys(this.options).map(name => {
        return this.validateField(name);
      });

      cb(result.every(bool => !!bool), this.state);
    };

    // 单个字段验证
    validateField = name => {
      const rules = this.options[name].rules;
      const isValid = !rules.some(rule => {
        if (rule.required) {
          if (!this.state[name]) {
            this.setState({
              [name + "Message"]: rule.message
            });
            return true;
          }
        }
        return false;
      });
      if (isValid) {
        this.setState({
          [name + "Message"]: ""
        });
      }
      return isValid;
    };

    // 获取错误信息 给FormItem使用
    getErrorMsg = name => {
      return this.state[name + "Message"];
    };

    // 包装表单控件,value和name由父级控制
    getFieldDecorator = (name, option, Com) => {
      this.options[name] = option;
      return (
        <div>
          {React.cloneElement(Com, {
            name: name,
            value: this.state[name] || "",
            onChange: this.handleChange
          })}
        </div>
      );
    };

    render() {
      return (
        <Com
          getFieldDecorator={this.getFieldDecorator}
          validateAllField={this.validateAllField}
          getErrorMsg={this.getErrorMsg}
        />
      );
    }
  };
}

// 装饰器组件 高阶组件的包装
// 使用组件
@MyFormCreate
class MyForm extends Component {
  // 提交  
  submitForm = () => {
    this.props.validateAllField((isValid, data) => {
      if (isValid) {
        console.log("登录:", data);
      } else {
        console.log("not valid");
      }
    });
  };

  render() {
    const { getFieldDecorator, getErrorMsg } = this.props;
    return (
      <div>
        <FormItem
          status={getErrorMsg("userName") ? "error" : ""}
          help={getErrorMsg("userName")}
        >
          {getFieldDecorator(
            "userName",
            {
              rules: [{ required: true, message: "用户名不能为空" }]
            },
            <input type="text" />
          )}
        </FormItem>

        <FormItem
          status={getErrorMsg("password") ? "error" : ""}
          help={getErrorMsg("password")}
        >
          {getFieldDecorator(
            "password",
            {
              rules: [{ required: true, message: "密码不能为空" }]
            },
            <input type="password" />
          )}
        </FormItem>

        <button onClick={this.submitForm}>登录</button>
      </div>
    );
  }
}

export default MyForm;

      

6. redux-thunk

redux-thunk 核心思想是判断是否是函数,如果是个函数action,就先执行,执行完成后就dispatch,如果不是函数是对象,就直接dispatch

// store/index.js

import { createStore, applyMiddleware, combineReducers } from "redux";

import logger from "redux-logger"; //logger组件

import thunk from "redux-thunk"; // 异步组件

import { countReducer } from "./counter"; // counter的reducer

import { userReducer } from "./user"; // user的reducer

const store = createStore(
  combineReducers({ count: countReducer, user: userReducer }), // reducer进行组合
  applyMiddleware(logger, thunk) // 引用中间件扩展,支持异步,先后顺序是执行顺序
);


export default store;
// redux需要用Provider来指定上下文,确定要传下去的store
// 下面具体内容,在下面React Router会解释


import React, { Component, lazy, Suspense } from "react";
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
import store from "./store";
import { Provider, connect } from "react-redux";

/**
 * 生成路由组件
 * @param {*} route
 */
export const SubRoute = route => {
  return route.private ? (
    <PrivateRoute path={route.path} component={route.component} />
  ) : (
    <Route
      path={route.path}
      render={props => <route.component {...props} routes={route.routes} />}
    />
  );
};

export const PrivateRoute = connect(state => ({ isLogin: state.user.isLogin }))(
  ({ component: Com, isLogin, ...rest }) => {
    return (
      <Route
        {...rest}
        render={props =>
          isLogin ? (
            <Com {...props} />
          ) : (
            <Redirect
              to={{
                pathname: "/login",
                state: { redirect: props.location.pathname }
              }}
            />
          )
        }
      />
    );
  }
);

export const lazyComponent = url => lazy(() => import(`${url}`));

const routes = [
  {
    path: "/",
    exact: true,
    component: lazyComponent("./Home")
  },
  {
    path: "/redux",
    component: lazyComponent("./ReduxTest")
  },
  {
    path: "/login",
    component: lazyComponent("./Login")
  },
  {
    path: "/about",
    component: lazyComponent("./About"),
    private: true
  },
  {
    path: "/detail/:types",
    component: lazyComponent("./Detail")
  }
];

class App extends Component {
  render() {
    return (
      // 下面进行store的传入
      <Provider store={store}>
        <BrowserRouter>
          <Suspense fallback={<div>loading...</div>}>
            <Switch>
              {routes.map((route, i) => (
                <SubRoute key={i} {...route} />
              ))}
            </Switch>
          </Suspense>
        </BrowserRouter>
      </Provider>
    );
  }
}

export default App;

 

 

// store/counter.js 

import { COUNT_ADD, COUNT_REDUCE } from "./types";

export const countReducer = (state = 0, action) => {
  switch (action.type) {
    case COUNT_ADD:
      return state + 1;
    case COUNT_REDUCE:
      return state - 1;
    default:
      return state;
  }
};
// store/user.js

import { REQUEST_LOGIN, LOGIN } from "./types";

export const userReducer = (state = { isLogin: false, loading: false }, action) => {
  switch (action.type) {
    case REQUEST_LOGIN:
      return {
          isLogin: false,
          loading: true
      };
    case LOGIN:
      return {
        isLogin: true,
        loading: false
    };
    default:
      return state;
  }
};

// action
export const login=()=>(dispatch)=>{
    dispatch({type: REQUEST_LOGIN});
    // 异步登录操作模拟
    setTimeout(() => {
        dispatch({type: LOGIN});
    }, 2000);
}
// store/types.js

// counter.js
export const COUNT_ADD='COUNT_ADD';
export const COUNT_REDUCE='COUNT_REDUCE';

// login.js
export const REQUEST_LOGIN='REQUEST_LOGIN';
export const LOGIN='LOGIN';
// ReduxTest.js  使用redux的组件

import React, { Component } from "react";
import { connect } from "react-redux"; // react和redux进行连接
import { COUNT_REDUCE, COUNT_ADD } from "./store/types";

const mapStateToProps = state => ({ num: state.count });
// state转化成props

const mapDispatchToProps = {
  add: () => ({ type: COUNT_ADD }),
  reduce: () => ({ type: COUNT_REDUCE }),
  asyncAdd: () => dispatch => {
    setTimeout(() => {
      dispatch({ type: COUNT_ADD });
    }, 1500);
  }
};
// action转化成props

// 这边用的是babel的高阶组件的装饰器
@connect(
  mapStateToProps,
  mapDispatchToProps
)
class ReduxTest extends Component {
  render() {
    const { num, add, reduce, asyncAdd } = this.props;
    return (
      <div>
        <p>{num}</p>
        <button onClick={add}>+</button>
        <button onClick={reduce}>-</button>
        <button onClick={asyncAdd}>async +</button>
      </div>
    );
  }
}

export default ReduxTest;

7. React Router

import React, { Component, lazy, Suspense } from "react";
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
import store from "./store";
import { Provider, connect } from "react-redux";

/**
 * 生成路由组件
 * @param {*} route
 */
export const SubRoute = route => {
  //如果是权限组件就用PrivateRoute的高阶组件,不是的话就直接是Route
  return route.private ? (
    <PrivateRoute path={route.path} component={route.component} />
  ) : (
    <Route
      path={route.path}
      render={props => <route.component {...props} routes={route.routes} />}
    />
  );
};

/**
 * 高阶组件,判断是否登录,登录了就直接跳转到路由指定的页面,没有登录就跳转登录页面
 * 跳转登录页面时候,把要登录后跳转的页面路径传过去
 * connect是react-redux的api,传入redux的用户信息isLogin
 */
export const PrivateRoute = connect(state => ({ isLogin: state.user.isLogin }))(
  ({ component: Com/*组件需要大写,做个命名的映射*/, isLogin, ...rest }) => {
    return (
      <Route
        {...rest}
        render={props =>
          isLogin ? (
            <Com {...props} />
          ) : (
            <Redirect
              to={{
                pathname: "/login",
                state: { redirect: props.location.pathname }
              }}
            />
          )
        }
      />
    );
  }
);

/**
 * 懒加载Api
 * import里面必须是字符串,要不然要报错
 * @param {*} url 
 */
export const lazyComponent = url => lazy(() => import(`${url}`));

// 页面的数组数据
const routes = [
  {
    path: "/",
    exact: true,
    component: lazyComponent("./Home")
  },
  {
    path: "/redux",
    component: lazyComponent("./ReduxTest")
  },
  {
    path: "/login",
    component: lazyComponent("./Login")
  },
  {
    path: "/about",
    component: lazyComponent("./About"),
    private: true
  },
  {
    path: "/detail/:types",
    component: lazyComponent("./Detail")
  }
];

/**
 * 路由没有匹配到,暂时展示这个
 * @param {*} param0 
 */
function NoMatch({ location }) {
  return (
      <h3>没有匹配到当前的路由:{location.pathname}</h3>
  )
}

// App组件,根级别
class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <BrowserRouter>
          <Suspense fallback={<div>loading...</div>}>
            {/*Switch 包含里面只能匹配一个*/}
            <Switch>
              {routes.map((route, i) => (
                <SubRoute key={i} {...route} />
              ))}
              {/*不指定路由,就会完美匹配*/}
              <Route component={NoMatch}></Route>
            </Switch>
          </Suspense>
        </BrowserRouter>
      </Provider>
    );
  }
}


export default App;

 

// Home.js

import React, { Component } from "react";
import { Link } from "react-router-dom";

export class Home extends Component {
  render() {
    return (
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        {/*to的Object形式,传数据到state,之后从location.state里面拿*/}
        <li>
          <Link
            to={{ pathname: "/redux", state: { msg: "这是个redux的组件" } }}
          >
            Redux
          </Link>
        </li>
        {/*about是个复合嵌套页面,里面自带路由*/}
        <li>
          <Link to="/about">About</Link>
        </li>
        {/*带参数的路由detail*/}
        <li>
          <Link to="/detail/history">Detail/History</Link>
        </li>
        <li>
          <Link
            to={{
              pathname: "/detail/location",
              state: { title: "当前是locaion" }
            }}
          >
            Detail/Location
          </Link>
        </li>
        <li>
          <Link to="/detail/match">Detail/Match</Link>
        </li>
      </ul>
    );
  }
}

export default Home;

 

// About.js
// 这是个嵌套复合页面
// 这是个权限路由,没有登录,会先去登录页面,登录好了在跳转过来,如果已经登录过了,就直接跳过来
// 下面的图片的console里面,就是登录的过程(redux-logger)

import React, { Component } from "react";
import { Link, Switch, Route, Redirect } from "react-router-dom";

class About extends Component {
  render() {
    return (
      <div>
        <h3>个人中心</h3>
        <p><Link to="/about/me">个人中心</Link></p>
        <p>
          <Link to="/about/order">订单系统</Link>
        </p>
        {/*嵌套路由*/}
        <Switch>
          <Route path="/about/me" component={() => <div>Me</div>}></Route>
          <Route path="/about/order" component={() => <div>订单</div>}></Route>
          <Redirect to="/about/me" />{/*上面都不满足,就跳转/about/me,比如/about*/}
        </Switch>
      </div>
    );
  }
}

export default About;

 

// Detail.js
// 这是参数不同的页面,动态

import React, { Component } from "react";

export class Detail extends Component {
  render() {
    const { history, location, match } = this.props;
    return <div>
        {match.params.types==='history' && <button onClick={history.goBack}>Histroy进行回退</button>}
        {match.params.types==='location' && <p>{JSON.stringify(location)}</p>}
        {match.params.types==='match' && <p>{JSON.stringify(match)}</p>}
    </div>;
  }
}

export default Detail;

 

// ReduxTest.js
// 前面已经介绍过

import React, { Component } from "react";
import { connect } from "react-redux";
import { COUNT_REDUCE, COUNT_ADD } from "./store/types";

const mapStateToProps = state => ({ num: state.count });

const mapDispatchToProps = {
  add: () => ({ type: COUNT_ADD }),
  reduce: () => ({ type: COUNT_REDUCE }),
  asyncAdd: () => dispatch => {
    setTimeout(() => {
      dispatch({ type: COUNT_ADD });
    }, 1500);
  }
};

@connect(
  mapStateToProps,
  mapDispatchToProps
)
class ReduxTest extends Component {
  render() {
    const { num, add, reduce, asyncAdd } = this.props;
    return (
      <div>
        <p>{num}</p>
        <button onClick={add}>+</button>
        <button onClick={reduce}>-</button>
        <button onClick={asyncAdd}>async +</button>
      </div>
    );
  }
}

export default ReduxTest;

 

// Login.js 上面的MyForm直接引用,加点逻辑

import React, { Component } from "react";
import { connect } from "react-redux";
import { login } from "./store/user";
import { Redirect } from "react-router-dom";

// 包含在空间外层的,form的直接元素,控制布局和样式之类的,错误消息提示
function FormItem(props) {
  const { children, status, help } = props;
  return (
    <div>
      {children}
      {status === "error" && (
        <p style={{ color: "red", fontSize: "12px" }}>{help}</p>
      )}
    </div>
  );
}

// 高阶组件 扩展传入组件功能,返回新的组件
// 增加字段的验证,所有字段的全局验证,表单组件的装饰器[值和name由父级控制]
function MyFormCreate(Com) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = {};
      this.options = {};
    }

    // 输入框的change事件

    handleChange = e => {
      const { value, name } = e.target;
 

      this.setState(
        {
          [name]: value
        },
        () => {
          this.validateField(name); //用回调方式,确保值变了,进行验证
        }
      );
    };

    // 全局验证,扩展出去,给使用组件调用
    validateAllField = cb => {
      const result = Object.keys(this.options).map(name => {
        return this.validateField(name);
      });

      cb(result.every(bool => !!bool), this.state);
    };

    // 单个字段验证
    validateField = name => {
      const rules = this.options[name].rules;
      const isValid = !rules.some(rule => {
        if (rule.required) {
          if (!this.state[name]) {
            this.setState({
              [name + "Message"]: rule.message
            });
            return true;
          }
        }
        return false;
      });
      if (isValid) {
        this.setState({
          [name + "Message"]: ""
        });
      }
      return isValid;
    };

    // 获取错误信息 给FormItem使用
    getErrorMsg = name => {
      return this.state[name + "Message"];
    };

    // 包装表单控件,value和name由父级控制
    getFieldDecorator = (name, option, Com) => {
      this.options[name] = option;
      return (
        <div>
          <p>{this.state[name]}---state</p>
          {React.cloneElement(Com, {
            name: name,
            value: this.state[name] || "",
            onChange: this.handleChange
          })}
        </div>
      );
    };

    render() {
      return (
        <Com
          {...this.props}
          getFieldDecorator={this.getFieldDecorator}
          validateAllField={this.validateAllField}
          getErrorMsg={this.getErrorMsg}
        />
      );
    }
  };
}

// 装饰器组件 高阶组件的包装
// 使用组件
@connect(
  state => ({ isLogin: state.user.isLogin, loading: state.user.loading }),
  { login }
)
@MyFormCreate
class MyForm extends Component {
  // 提交
  submitForm = () => {
    this.props.validateAllField((isValid, data) => {
      if (isValid) {
        console.log("登录:", data);
        console.log(this.props)
        this.props.login();
      } else {
        console.log("not valid");
      }
    });
  };

  render() {
    const { getFieldDecorator, getErrorMsg, isLogin, location, loading } = this.props;
    if (isLogin) {
      return <Redirect to={location.state.redirect || "/"} />;
    }
    return (
      <div>
        <FormItem
          status={getErrorMsg("userName") ? "error" : ""}
          help={getErrorMsg("userName")}
        >
          {getFieldDecorator(
            "userName",
            {
              rules: [{ required: true, message: "用户名不能为空" }]
            },
            <input type="text" />
          )}
        </FormItem>

        <FormItem
          status={getErrorMsg("password") ? "error" : ""}
          help={getErrorMsg("password")}
        >
          {getFieldDecorator(
            "password",
            {
              rules: [{ required: true, message: "密码不能为空" }]
            },
            <input type="password" />
          )}
        </FormItem>

        <button onClick={this.submitForm} disabled={loading}>{loading?'登录中...':'登录'}</button>
      </div>
    );
  }
}

export default MyForm;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值