React学习-基础知识梳理

311 阅读13分钟

前言

总结了一下最近开发中用到的相关技能点,这里从应用层面入手,梳理一了遍各个知识点在实际开发中的使用方法,也总结了一些常用的解决方案。本意是给自己做一下阶段性总结,如果能帮到各位同学,那也是极好的。

class组件创建

这是一个基本class组件的创建。

import React, {Component} from 'react';

class App extends Component{
    render(){
        return <div>这是一个组件</div>;
    };
}

export default App;

class组件的生命周期

  1. constructor()

    完成了React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。 注意:只要使用了constructor()就必须写super(),否则会导致this指向错误。

  2. componentWillMount()

    在数据初始化完毕后,DOM未渲染时调用。

  3. componentDidMount()

    在DOM节点生成后调用。

  4. componentWillUnmount()

    组件卸载数据销毁时调用,在这里进行监听、定时的移除。

  5. componentWillReceiveProps(nextProps)

    当前组件接收父组件props数据重新渲染时调用,在初始props不会被调用,nextProps是新传入的props参数

    // 代替方案
    static getDerivedStateFromProps(nextProps,prevState){
       //该方法内禁止访问this
       if(nextProps.email !== prevState.email){
         //通过对比nextProps和prevState,返回一个用于更新状态的对象
         return {
    				value:nextProps.email
         }
       }
       //不需要更新状态,返回null
       return null;
    }
    
    

    使用了getDerivedStateFromProps后是无法访问this的,如果需要访问的话可以配合componentDidUpdate去使用

    componentDidUpdate(prevProps, nextProps){
    	// this..
    }
    
  6. shoudlComponentUpdate

    减少组件重复渲染,提高界面性能

    shouldComponentUpdate(nextProps, nextState){
    	return false;
    }
    

渲染列表

import React, {Component} from 'react';

class List extends Component {
	constructor(props){
    	super(props);
        this.state = {
        	list: [
                  { key: 0, name: '张三' },
                  { key: 1, name: '李四' }
            ]
        }
    }
    render(){
    	return (
        	<ul>
            	{
                	this.state.list.map(item => {
                    	return <li key={item.key}>{item.name}</li>;
                    })
                }
            </ul>
        );
    }
}

修改state数据

注意:setState是个异步处理,要避免赋值后马上读取state数据,更不要直接this.state.xx = 'xx'

//this.setState({}, e=>{})
this.setState((state, props)=>{
    return {
        proptypeA: 'valA',
        proptypeB: 'valB'
    }
});
this.setState=({protype: 'xxxA}, _ => {
		console.log('这里是异步回调');          
});

插入富文本

render(){
    return (
    	<div dangerouslySetInnerHtml = {__html:"<p>123</p>"}
    );
}

获取DOM元素

react提供了ref来获取dom元素

//render部
<button ref="myButton" onClick={this.handleClick.bind(this)}>测试按钮</button>

//绑定的事件函数
handleClick = ()=>{
	console.log(this.refs.myButton)
}

数据双向绑定

class login extends Component{
    constructor(props){
        super(props);
        this.state = {
            userName: ''
        }
    }

   handleChange(e, event){
        for(let item in this.state){
            if(item == e){
                this.state[item]=event.target.value;
                this.setState(this.state)
            }
        }
    }

    render(){
        return(
            <div>
                <input 
                	type="text"
                  value={this.state.userName}
                  onChange={this.handleChange.bind(this, 'userName')}>
                </input>
            </div>
        )
    }
    
}

阻止事件冒泡

handleCoverClick = ()=>{
	console.log('handle cover click!')
}
handleButtonClick = (param, e)=>{
	e.stopPropagation()	//阻止事件冒泡
	console.log(param)
}

<div onClick={ this.handleCoverClick }>
	<button onClick={ e => this.handleButtonClick('handle button click!', e) }></button>
</div>

动态绑定className

利用模板字符串实现

<div className={`classA ${a===b?'classB':''}`}><//div>

实现scoped即styled-components的使用

styled-components实现了使用js文件来处理css样式,同时也解决了组件间样式冲突的问题。

安装

npm i styled-components --s

样式文件

import styled from 'styled-components';
import picUrl from 'xxx/xx/xxx.png';
const HomeWrapper = styled.div `
	background-color: red;
	background-image: url(${picUrl}); // 引入图片
	color: url(${props=>props.color}); // 使用组件传值
`;
export {
	HomeWrapper
}

组件中使用

import React, Component from 'react';
import {HomeWrapper} from './styledHome';
class Home extends Component{
    render(){
        return(
        	<HomeWrapper color="#fff"></HomeWrapper>
        )
    }
}

其他详细内容移步styled-components.com/

父组件向子组件传参

//父组件
<ButtonUi type="primary" text="主要按钮"></ButtonUi>
//子组件
class buttonUi extends Component{
    render(){
    	const {type, text} = this.props;
        return(
            <button className={`${type}`}>{text}</button>
        )
    }
}

子组件向父组件传参

//子组件
class buttonUi extends Component{
    click(text){
    	let {click} = this.props;
        if (click && typeof click === 'function') {
        	click(text);
        }
    }
  
    render(){
        return(
            <button onClick={this.click.bind(this, '传递的参数')}></button>
        )
    }
}
//父组件
class father extends Component{
  handleClick(text){
    console.log('传递的参数是: ', text);
  }
  
  render(){
  	return (
    	<ButtonUi click={this.handleClick.bind(this)}></ButtonUi>
    );
  }
}

使用pubsub实现兄弟组件间传参

安装

npm i pubsub-js --s

使用

兄弟组件1

import Pubsub from 'pubsub-js';
class Test1 extends Component {
    render() {
        return (
            <div>
                <button onClick={()=>this.handleClick()}>test1</button>
            </div>
        )
    }
    handleClick(){
        Pubsub.publish('emit', 'test1')
    }
}

兄弟组件2

class Test2 extends Component {
    constructor(props){
        super(props);
        this.state = {
            name: 'Test2'
        }
        Pubsub.subscribe('emit', (msg, data)=>{
            this.setState({
                name: data
            })
        })
    }
    render() {
        return (
            <div>
                <h1>{this.state.name}</h1>
            </div>
        )
    }
}

后面会介绍react-redux,hook useContext来实现兄弟组件公用数据。

props验证与默认值

函数组件

import React from 'react';
import PropTypes from 'prop-types';
const Test = function(props){
    return <div>{props.name}</div>
}
Test.defaultProps = {
    name: '张三'
}
Test.propTypes = {
    name: PropTypes.stringmsg: PropTypes.string.isRequired // 追加一个非空验证
}

类组件

import React, Component from 'react';
import PropTypes from 'prop-types';
class Test extends Component{
    static defaultProps = {
        name: '张三'
    }
		static propTypes = {
        name: PropTypes.string
    }
    return <div>{props.name}</div>;
}

这里只做用法介绍,详情请参考

zh-hans.reactjs.org/docs/typech…

实现slot插槽功能

//父组件
import React, {Component} from 'react';
import './jbutton.scss';
import CodeUi from '../../components/codeUi/codeUi';

class jbutton extends Component{
    render(){
        return(
            <div className="jbutton-container">
                <CodeUi>
                   <div>自定义扩展内容</div>
                </CodeUi>
            </div>
        );
    }
}
export default jbutton;
//子组件
import React, { Component } from 'react';
import './codeUi.scss';

class codeUi extends React.Component{
    render(){
        return(
            <div>
                <div className="code-container">
                    <div className="icon">显示代码</div>
                </div>
                <div className="code-content">
                    {this.props.children.map((item, index)=>{
                        return (<div key={index}>{item.props.children}</div>)
                    })}
                </div>
            </div>
        );
    }
}
export default codeUi;

路由介绍

Router组件

Router组件是一个容器,路由是需要Route进行定义。

import React from 'react';
import {Router} from 'react-router';

const App = () => {
  return (
  	<Router>
      {/* .... */}
    </Router>
  );
};

export default App;

Route, 配合Router容器使用

import React from 'react';
import {Router, Route, browserHistory} from 'react-router';
import LayOut from 'xxx/Layout';
import Login from 'xxx/Login';

const App = () => {
  return (
  	<Router history={browserHistory}>
      <Route path='/' component={LayOut}></Route>
      <Route path='/login' component={Login}></Route>
    </Router>
  );
};

export default App;

Redirect

作用是访问一个路由,会自动跳转到另一个路由。

import React from 'react';
import {Router, Route, Redirect, browserHistory} from 'react-router';
import LayOut from 'xxx/Layout';
import Login from 'xxx/Login';
import Main from 'xxx/Main';

const App = () => {
  return (
  	<Router history={browserHistory}>
      <Route path='/' component={LayOut}></Route>
      <Route path='/login' component={Login}></Route>
      <Route path='/main' component={Main}></Route>
      <Redirect from='/main/index' to='/main/:id'></Route>
    </Router>
  );
};

export default App;

路由嵌套

访问/main/about时,会先加载Main组件,然后在它的内部再加载About组件。

import React from 'react';
import {Router, Route, Redirect, browserHistory} from 'react-router';
import Main from 'xxx/Main';
import Detail from 'xxx/Detail';
import About from 'xxx/About';

const App = () => {
  return (
  	<>
    	<Router history={browserHistory}>
        <Route path="/main" component={Main}>
          <Route path="/detail" component={Detail}/>
          <Route path="/about" component={About}/>
        </Route>
      </Router>
    </>
  );
};

export default App;

路由的钩子

可以使用onEnter来做认证。


const requireAuth = (nextState, replace) => {
    if (!auth.isAdmin()) {
        replace({ pathname: '/' })
    }
};
export const AdminRoutes = () => {
  return (
     <Route path="/admin" component={Admin} onEnter={requireAuth} />
  );
}

路由跳转和传参

跳转

this.props.history.push('/home/jtable');

传参

// 跳转时传参
this.props.hisroy.push({pathname: '/index/home', query:{id:'admin'}})
// 获取参数
this.props.location.query

路由信息获取

this.props.location.pathname

react-router + react-router-dom + react-router-config快速配置路由

安装依赖

npm install react-router --s
npm install react-router-dom --s
npm install react-router-config --s

创建配置文件

import login from '../pages/login/login';
import home from '../pages/home/home';

const routes = [
    {
        path: '/',
        component: login,
        exact: true
    }
];

export default routes;

App.js根组件引入使用

这里使用了react-router-config第三方工具进行路由渲染,当然也可以自己实现高阶组件进行自定义路由渲染。

import React from 'react';
import './App.scss';
import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import routes from './router/index'

const App = () => {
  return (
    <div className="App">
      <BrowserRouter>
        {renderRoutes(routes)}
      </BrowserRouter>
    </div>
  );
};

export default App;

配置路由嵌套

//路由配置
{
    path: '/home',
    component: home,
    children:[
       {
           path: '/home/jtable',
           component: jtable
       },
       {
           path: '/home/jbutton',
           component: jbutton
       }
    ]
}
//嵌套页面
import React, {Component} from 'react';
import { Link } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';

class home extends Component{
    constructor(props){
        super(props)
        this.state = {
            route: props.route.children
        }
    }

    render(){
        const route = this.state.route
        return(
            <div>
                <h1>home!</h1>
                <div>
                    <Link to="/home/jtable">jtable</Link>
                </div>
                <div>
                    {renderRoutes(route)}
                </div>
            </div>
        )
    };
}

export default home;

路由懒加载配置

//安装依赖
npm install react-loadable
//routes模块配置
import React from 'react';
import Loadable from 'react-loadable';

const Loading = function(){
    return <div>loading...</div>;
};

const routes = [
    {
        path: '/',
        component: Loadable({
            loading: Loading,
            loader:()=>import('../login/login')
        }),
        exact: true
    },
    {
        path: '/home',
        component: Loadable({
            loading: Loading,
            loader:()=>import('../home/home')
        })
    }
];

export default routes;

react-redux

//安装相关包,react-redux依赖于redux,redux和react-redux必须一起安装
npm install redux --save
npm install react-redux --save

目录结构

--store
	--reducers // 自定义模块
		--user.js
		--common.js
	--index.js // 整合store出口
--index.js

store/reducers/user.js

const initState = {
    account: localStorage.getItem('account') || ''
};

const reducer = (state = initState, action) => {
    switch(action.type){
        case 'LOGIN':
            localStorage.setItem('account', action.val)
            return {...state}
        case 'LOGOUT':
            localStorage.removeItem('account')
            return {...state}
        default:
            return {...state}
    }
};

export default reducer;

/store/index.js模块导出

// createStore方法是用来创建store的,combineReducers方法是用来合并多个reducer的
import { createStore, combineReducers } from 'redux';
import userReducer from './reducers/user';

// 创建根reducer,利用combineReducers合并多个reducer,此处还未定义reducer,所以暂空
const rootReducer = combineReducers({
    userReducer
});

// 创建初始化的state,初始化为一个空对象即可,默认的数据建议都写在reducer上
const initState = {};

// 创建store,第一个参数是根reducer,第二个参数可以是初始化的state,也可以是别的,暂且不提
const store = createStore(rootReducer, initState);

export default store;

/index.js全局引入

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './store/index';

ReactDOM.render(
  // 全局挂载
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

组件中使用

import React, {Component} from 'react';
import './login.scss';
import { Form, Button, Input, message } from 'antd';
import { UserOutlined, LockOutlined  } from '@ant-design/icons';
// 引入connect
import { connect } from 'react-redux';


class login extends Component{
    constructor(){
        super()
        this.state = {
            form: {
                account: '',
                password: ''
            }
        }
    }

    // 渲染部
    render(){
        return(
            <div className="login-container">
                <div>{this.props.account}</div>
                <div className="login-form">
                    <div className="form-header">
                        <h2>系统登录</h2>
                    </div>
                    <Form name="basic">
                        <Form.Item name="account" rules={[{ required: true, message: '请输入账户名' 							}]}>
                            <Input 
                                size="large" 
                                placeholder="请输入账户名" 
                                prefix={<UserOutlined/>} 
                                maxLength="20"
                                value={this.state.form.account}
                                onChange={this.onAccountChange}>
                            </Input>
                        </Form.Item>
                        <Form.Item name="password" rules={[{ required: true, message: '请输入密码' 							}]}>
                            <Input.Password 
                                size="large" 
                                placeholder="请输入密码" 
                                prefix={<LockOutlined/>} 
                                maxLength="20"
                                value={this.state.form.password}
                                onChange={this.onPasswordChange}>
                            </Input.Password>
                        </Form.Item>
                        <Form.Item>
                            <Button type="primary" onClick={()=> this.login()}>登录</Button>
                        </Form.Item>
                    </Form>
                    <div className="form-item form-bottom">
                        <div className="bottom-left">
                            <div className="left-item">账号:admin</div>
                            <div className="left-item">密码:admin1</div>
                        </div>
                        <div className="bottom-right">
                            <Button type="primary">第三方登录</Button>
                        </div>
                    </div>
                </div>
            </div>
        )
    }

    // 登录
    login = ()=>{
        if(this.state.form.account != 'admin' || this.state.form.password != 'admin1'){
            message.warning('账号或密码错误!')
            return
        }
        this.props.Login(this.state.form.account)
        this.props.history.push('/home')
    }
    
    //账号数据绑定
    onAccountChange = ({target: {value}})=>{
        this.setState(state=>{
            return {
                form: {
                    account: value,
                    password: this.state.form.password
                }
            }
        })
    }
    
    // 密码数据绑定
    onPasswordChange= ({target: {value}})=>{
        this.setState(state=>{
            return {
                form: {
                    account: this.state.form.account,
                    password: value
                }
            }
        })
    }
}

// state映射
function mapStateToProps(state) {
   return {
       account: state.userReducer.account
   }
}
// dispatch映射
function mapDispatchToProps(dispatch) {
    return {
      Login: e => {
        dispatch({ type: 'LOGIN', val: e})
      }
    }
}

// 注意修改 除了这种写法也可使用装饰器
export default connect(mapStateToProps, mapDispatchToProps)(login);

react-saga使用方法

react-saga是帮助react-redux实现异步的一种解决方案。

界面提交action=>saga, saga接收action异步处理后再提交一个新的action=>reducer处理。

安装

npm i redux-saga --s

目录结构

--pages
    --hotcat
        --HotCat.jsx //界面
    --store
        --reducer
            --common.js // reducer
        --index.js	// store整合导出文件
        --sagas.js	// saga文件
... // 此处省略
--index.js

HotCat.jsx界面

import React, { Component } from 'react';
import { connect } from 'react-redux';

class HotCat extends Component {
    componentDidMount(){
        this.props.setList();
    }
    
    render() {
        return <div>hot cat</div>;
    }
}

function mapStateToProps(state){
    return{
        list: state.common.list
    };
};
function mapDispatchToProps(dispatch){
    return{
        setList: e=>dispatch({
            type: 'INIT_LIST'
        })
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(HotCat);

saga.js文件

import { 
    takeEvery, // 所有异步函数依次执行
    takeLatest, // 短时间内执行多次异步操作只返回最先完成的任务,其他任务中止
    throttle,   // 类似节流 使用方法类似
    select, 
    call, 
    put 
} from 'redux-saga/effects';
import request from '../utils/request/index';

// 执行函数
const getInitList = params => {
    console.log(params); // 输出this is a params
    return request.get('/api/hotCatData')
}

// 代码生成器
const saga = function* initList() {
    yield takeEvery('INIT_LIST', function* (e) {
        // 获取state数据
        let state = yield select(state => state.common);
        // 调用函数
        let result = yield call(getInitList, 'this is a params');
        // 提交一个新的action给reducer
        yield put({
            type: 'INIT_SUCCESS',
            val: result.data
        })
    });
}

export default saga;

common store文件

const initState = {
    list: []
}

const reducer = (state=initState, action)=>{
    switch(action.type){
        // 接收action并区分处理
        case 'INIT_SUCCESS':
            state.list = action.val;
            return {...state};
        default:
            return {...state, ...action};
    }
}

export default reducer;

store/index.js整合配置redux-saga, 并导出reducer文件

import { createStore, combineReducers, applyMiddleware } from 'redux';
import createSagaMiddleWare from 'redux-saga';
import common from './reducer/common';
import sagas from './sagas';

// 获取redux-saga中间件实例
const sagaMiddleWare = createSagaMiddleWare();

const rootReducer = combineReducers({
    common
})
// 把中间件注入到store中
const store = createStore(
    rootReducer,
    applyMiddleware(sagaMiddleWare)
);
// 执行sagas.js文件
sagaMiddleWare.run(sagas);

export default store;

/index.js全局使用store

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Provider} from 'react-redux';
import store from './store/index';

ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.getElementById('root')
);

react-router + react-router-config + react-redux 实现登录拦截

目录结构

---login
	---login.js	//登录界面
	---login.scss
---store
	---reducers
		---user.js	//全局 user store
	---index.js
---router
	---index.js		//路由配置文件
	---renderRoutes.js	//路由渲染函数
---App.js	//根页面

登陆界面 (页面中包含ant-design, 请自行引入)

import React, {Component} from 'react';
import './login.scss';
import { Form, Button, Input, message } from 'antd';
import { UserOutlined, LockOutlined  } from '@ant-design/icons';
import { connect } from 'react-redux';

class login extends Component{
    constructor(){
        super();
        this.state = {
            form: {
                account: '',
                password: ''
            }
        }
    }

    // 渲染部
    render(){
        return(
            <div className="login-container">
                <div className="login-form">
                    <div className="form-header">
                        <h2>系统登录</h2>
                    </div>
                    <Form name="basic">
                        <Form.Item name="account" rules={[{ required: true, message: '请输入账户名' }]}>
                            <Input 
                                size="large" 
                                placeholder="请输入账户名" 
                                prefix={<UserOutlined/>} 
                                maxLength="20"
                                value={this.state.form.account}
                                onChange={this.onAccountChange}>
                            </Input>
                        </Form.Item>
                        <Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
                            <Input.Password 
                                size="large" 
                                placeholder="请输入密码" 
                                prefix={<LockOutlined/>} 
                                maxLength="20"
                                value={this.state.form.password}
                                onChange={this.onPasswordChange}>
                            </Input.Password>
                        </Form.Item>
                        <Form.Item>
                            <Button type="primary" onClick={()=> this.login()}>登录</Button>
                        </Form.Item>
                    </Form>
                    <div className="form-item form-bottom">
                        <div className="bottom-left">
                            <div className="left-item">账号:admin</div>
                            <div className="left-item">密码:admin1</div>
                        </div>
                        <div className="bottom-right">
                            <Button type="primary">第三方登录</Button>
                        </div>
                    </div>
                </div>
            </div>
        )
    }

    // 登录
    login = ()=>{
        if(this.state.form.account != 'admin' || this.state.form.password != 'admin1'){
            message.warning('账号或密码错误!')
            return
        }
        // ---重点, 登录时全局添加登录标识
        this.props.Login(this.state.form.account)
        this.props.history.push('/index/home')	//跳转
    }
    
    //账号数据绑定
    onAccountChange = ({target: {value}})=>{
        this.setState(state=>{
            return {
                form: {
                    account: value,
                    password: this.state.form.password
                }
            }
        })
    }
    
    // 密码数据绑定
    onPasswordChange= ({target: {value}})=>{
        this.setState(state=>{
            return {
                form: {
                    account: this.state.form.account,
                    password: value
                }
            }
        })
    }
}

function mapStateToProps(state) {
   return {
       account: state.userReducer.account
   }
}
//---重点, react-redux映射函数
function mapDispatchToProps(dispatch) {
    return {
      Login: () => {
        dispatch({ type: 'LOGIN' })
      }
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(login);

user store (/store/reducers/user.js)

// 全局state
const initState = {
    account: localStorage.getItem('account') || ''
};

// 全局reducer
const reducer = (state = initState, action) => {
    switch(action.type){
        // 登录
        case 'LOGIN':
            state.account = action.val;
            localStorage.setItem('account', action.val);
            return {...state};
        // 登出
        case 'LOGOUT':
            state.account = '';
            localStorage.removeItem('account');
            return {...state};
        default:
            return {...state};
    }
}

export default reducer;

store模组导出函数(/store/index.js)

// createStore方法是用来创建store的,combineReducers方法是用来合并多个reducer的
import { createStore, combineReducers } from 'redux';
import userReducer from './reducers/user';

// 创建根reducer,利用combineReducers合并多个reducer,此处还未定义reducer,所以暂空
const rootReducer = combineReducers({
    userReducer
});

// 创建初始化的state,初始化为一个空对象即可,默认的数据建议都写在reducer上
const initState = {};

// 创建store,第一个参数是根reducer,第二个参数可以是初始化的state,也可以是别的,暂且不提
const store = createStore(rootReducer, initState);

export default store;

路由配置文件(/router/index.js)

import React from 'react';
import Loadable from 'react-loadable';

const Loading = function(){
    return <div></div>;
};

const routes = [
    {
        path: '/',
        component: Loadable({
            loading: Loading,
            loader: ()=>import('../pages/login/login')
        }),
        exact: true,
        requiresAuth: false	//---重点,是否需要权限
    },
    {
        path: '/index',
        component: Loadable({
            loading: Loading,
            loader: ()=>import('../pages/index/index')
        }),
        exact: false,
        requiresAuth: true, 	//---重点,是否需要权限
        children: [
            {
                path: '/index/home',
                component: Loadable({
                    loading: Loading,
                    loader: ()=>import('../pages/home/home')
                }),
                exact: false,
                requiresAuth: true 	//---重点,是否需要权限
            },
            {
                path: '/index/doc',
                component: Loadable({
                    loading: Loading,
                    loader: ()=>import('../pages/doc/doc')
                }),
                exact: false,
                requiresAuth: true 	//---重点,是否需要权限
            }
        ]
    }
];

export default routes;

自定义路由渲染函数(/router/renderRoutes)

import React from 'react'
import { Route, Redirect, Switch } from 'react-router-dom'
//---重点,routes:路由配置信息,authed:是否有跳转权限, authPath:没有跳转权限时的强制跳转地址
const renderRoutes = (routes, authed, authPath = authPath, extraProps = {}, switchProps = {}) => routes ? (
  <Switch {...switchProps}>
    {routes.map((route, i) => (
    	let {key, path, exact, strict} = route;
      <Route
        key={key || i}
        path={path}
        exact={exact}
        strict={strict}
        render={(props) => {
          if (!route.requiresAuth || authed || route.path === authPath) {
            return <route.component {...props} {...extraProps} route={route} />
          }
          return <Redirect to={{ pathname: authPath, state: { from: props.location } }} />
        }}
      />
    ))}
  </Switch>
) : null
 
export default renderRoutes;

根页面(/App.js)

import React, { Component } from 'react';
import './App.css';
import { BrowserRouter } from 'react-router-dom';
//引入自定义路由渲染函数
import renderRoutes from './router/renderRoutes'
//react-redux连接函数
import { connect } from 'react-redux'
//路由配置文件
import routes from './router/index'

class App extends Component{
  render(){
    return (
      //因为鉴权是动态的所以要动态赋值,三步式写在函数内部
      <BrowserRouter>
        { renderRoutes(routes, this.props.account?true:false, '/') }
      </BrowserRouter>
    )
  }
}

// 鉴权状态映射
function mapStateToProps(state) {
  return {
      account: state.userReducer.account
  };
};
export default connect(mapStateToProps)(App);

使用Echarts图标渲染数据

安装

npm install echarts-for-react --save
npm install echarts --save

使用

import React, {Component} from 'react'
import './home.scss'

//按需加载
import echarts from 'echarts/lib/echarts'
//引入主题样式 可以从官网下载主题json文件作为js对象导出然后在这里引用
//官网地址: https://echarts.apache.org/zh/download-theme.html
import theme from './primary'	

class home extends Component{
    //要在dom元素加载完毕后初始化echarts表格
    componentDidMount = ()=>{
      let myEchart = echarts.init(this.refs.myEchart, theme)	//初始化
      myEchart.setOption(this.getOption())	//数据设定
    }
    
    getOption =()=> {
        let option = {
          title:{
            text:'用户骑行订单',
            x:'center'
          },
          tooltip:{
            trigger:'axis',
          },
          xAxis:{
            data:['周一','周二','周三','周四','周五','周六','周日']
          },
          yAxis:{
            type:'value'
          },
          series:[
            {
              name:'OFO订单量',
              type:'line',   //这块要定义type类型,柱形图是bar,饼图是pie
              data:[1000,2000,1500,3000,2000,1200,800]
            }
          ]
        }
       return option
      };
    
    render(){
        return(
          <div>
            <div ref="myEchart" style={{width:800, height:400}}></div>
          </div>
        );
    }
}

export default home;

函数组件

import React from 'react;

const App = props => {
	return <div>这是一个组件</div>;
};

export default App;

函数组件与类组件的区别

函数组件与类组件返回的都不是html无法直接对其进行渲染,其本质返回的都是与dom对应的对象,页面根据这个dom对象进行实际渲染。

函数组件使用时无需像类组件一样需要实例化,所以尽量使用函数组件来提高性能。 类组件则相反,使用时需要实例化。

区别函数组件类组件
是否有this没有
是否有生命周期没有
是否有状态state没有

Hooks

hooks解决了函数组件无状态的问题,使得函数组件也可以像类组件一样的使用。

useState

让函数组件可以使用状态数据。

import React, { useState } from 'react';
function example(){
    const [count, setCount] = useState(0)
    return (
    	<p>{count}</p>
        <button onClick={()=>{
        	setCount(count + 1)
        }}></button>
    )
}

useEffect

useEffect就好比componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个生命周期的组合。

结合useState举例。

import React, {useState, useEffect} from 'react';

const App = props => {
  const [count, addCount] = useState(0);
  
  useEffect(() => {
    addCount(count + 1);
  });
};

export default App;

执行后会出现无限循环的情况。因为useEffect在组件mount时或者组件更新时执行, 如果只想在组件mount时请求数据,可以传递一个空数组作为useEffect的第二个参数。

useEffect(() => {
  addCount(count + 1);
}, []);

除此之外,useEffect的第二个参数可用于定义其依赖的所有变量,如果其中有变量发生变化,那么useEffect会再次运行。

import React, {useState, useEffect} from 'react';

const App = props => {
  const [bean, setBean] = useState(false);
  const [count, addCount] = useState(0);

  useEffect(() => {
    addCount(count + 1);
  }, [bean]);

  return (
    <>
      <h1>{count}</h1>
      <button onClick={setBean(!bean)} />
    </>
  );
};

export default App;

如果想在useEffect中执行异步操作呢?

import React, {useState, useEffect} from 'react';
import axios from 'axios';

const App = props => {
  const [count, setCount] = useState(count);
  useEffect(async () => {
    let res = await axios.get('http://xxxxx');
		setCount(res);
  }, []);
  return <h1>{count}</h1>
};

export default App;

如上这种写法是会报错的,因为async, await返回的是一个promise对象,而useEffect的第一个参数接收的是一个函数,所以可以用如下这种写法;

// ...
useEffect(() => {
  const toDo = async () => {
    let res = await axios.get('http://xxx');
    setCount(res);
  };
  toDo();
}, []);

再看一下如何在useEffect中使用并清除定时器。

useEffect(() => {
	const timer = setInterval(() => {
    	console.log(new Date().getTime());
    }, 1000);
    return () = {
    	clearInterval(timer);
    };
}, []);

useMemo

useMemouseEffect很像,区别在于,useEffect可以处理副作用,也就是说如果只想在依赖变更时执行逻辑,那么就可以使用useMemo
注意: 不要在useMemo中修改state数据,因为useMemo是在渲染中进行的, 否则会出现死循环现象。

import React, {useState, useEffect, useMemo} from 'react';

const App = props => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('李四');
  
  const getNameByNormal = () => {
  	console.log('get name by normal');
  	return `${name} normal`;
  };
  
  useEffect(() => {
  	console.log('count effect');
  }, [count]);
  
  const getNameByMemo = useMemo(() => {
    console.log('get name by memo');
    return `${name} meo`
  }, [name]);
  
  return (
  	<>
    	<h1>{getNameByNormal()}</h1>
    	<h1>{getNameByMemo()}</h1>
        <button onClick={setCount(count + 1)}>add count</button>
    	<button onClick={setName('张三')}>set name</button>
    </>
  );
};

export default App;

点击add count按钮时,打印get name by normal, count effect

点击set name按钮时,打印get name by memo, get name by normal

使用方法如下

const [name, setName] = useState('');

const getName = useMemo(() => {
  return `${name} memo`;
}, [name]);

return (
	<>
  	<h1>{getName}</h1>
  	<button onClick={setName('李四')}set name</button>
  </>
);

useContext

让函数组件间共享状态

import React, {createContext, useContext} from 'react';
const StoreContext = createContext({});
const state = {
  name: '张三',
  age: '20'
};
// 根组件
function App(){
  return (
    {/* 注入全局数据 */}
  	<StoreContext.Provider value = {state}>
    </StoreContext.Provider>
  )
}
// 子组件1
function Child1(){
	const {name, age} = useContext(StoreContext);
  return (
  	<ul>
      <li>name</li>
      <li>age</li>
    </ul>
  )
}
// 子组件2... 一样可以以这种方式共享数据

useReducer

看了useContext的使用场景后,发现hooks也许可以实现react-redux的数据处理方式。在用useContext实现了全局状态共享后,再使用useReducer实现组件内部的数据提交。

import React, {createContext, useContext, useReducer} from 'react';
const StoreContext = createContext({});
const initState = {
  name: '张三',
  age: 20
}
function rootReducer(state, action){
  switch(action.type){
    case 'CHANGE':
      return {
        name: action.val,
        ...state
      }
    default:
      return state
  }
}

// 根组件
function App(){
  const [state, dispatch] = useReducer(rootReducer, initState);
  return (
    {/*全局注入state和dispatch*/}
  	<StoreContext.Provider value = {{
        state,
        dispatch
      }}>
    </StoreContext.Provider>
  )
}
// 子组件1
function Child1(){
	const {state, dispatch} = useContext(StoreContext);
  handleClick = ()=>{
    dispatch({
      type: 'CHANGE',
      val: '李四'
    });
  }
  return (
  	<div>
      <h1>{state.name}</h1>
      <button onClick = {handleClick}>dispatch</button>
    </div>
  )
}

同样的,如果想实现一个异步操作那么在提交action时,在负载payload中添加一个promise异步函数,然后在提交action时执行payload中的异步函数,执行后再dispatch一个actionreducer即可。

如此一来hooks配合函数组件基本就实现了`react-redux的功能。

useRef

可以用来获取dom对象, 或者跨渲染周期保存数据。

import React, { useState, useEffect, useMemo, useRef } from 'react';

const App = props => {
  const [count, setCount] = useState(0);
  const couterRef = useRef(); // 保存dom
  const customRef = useRef(100); // 保存数据

  const addCount = useMemo(() => {
    return count + 1;
  }, [count]);

  useEffect(() => {
    console.log(`dom value is ${couterRef.current.target.value}`);
  }, [count]);
  
  return (
    <>
      <button
        ref={couterRef}
        onClick={() => {setCount(count + 1)}}
      >
      	Count: {count}, addCount: {addCount}
    	</button>
    </>
  );
};

export default App;

使用装饰器decorators

安装

npm i @babel/plugin-proposal-decorators --s

config.overrides.js中配置

const { override, fixBabelImports, addWebpackAlias, addDecoratorsLegacy } = require('customize-cra');

module.exports = function override(config, env) {
  return config;
};
module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd-mobile',
    style: 'css'
  }),
  addDecoratorsLegacy()
);

在组件中配合react-redux使用

import React, { Component } from 'react';
import { HotCatMain } from './styleCookBook';
import { connect } from 'react-redux';

// @connect(mapStateToProps, mapDispatchToProps)
// 或者把map函数直接写到connect装饰器中来
@connect(
    state => ({
        list: state.common.list
    }),
    dispatch => ({
        setList(e) {
            dispatch({
                type: 'INIT_LIST',
                val: e
            })
        }
    })
)
class HotCat extends Component {
    componentDidMount() {
        this.props.setList();
    }

    render() {
        return (
            <HotCatMain>
                {
                    this.props.list.map(item => {
                        return (
                            <div key={item.id}>
                                <img src={item.picUrl}></img>
                                <span>{item.name}</span>
                            </div>
                        )
                    })
                }
            </HotCatMain>
        )
    }
}

// function mapStateToProps(state){
//     return{
//         list: state.common.list
//     }
// }
// function mapDispatchToProps(dispatch){
//     return{
//         setList: e=>dispatch({
//             type: 'INIT_LIST'
//         })
//     }
// }

export default HotCat;