react进阶

1. 虚拟dom

来看下对比:


image
image

react native可以开发原生应用的原因是因为虚拟dom,在浏览器中虚拟dom可以生成网页,在原生应用中可以生成其他组件
虚拟dom比对,diff算法,同级比对

image

当最上层的节点就不一致时,react就不会往下比对了,直接删掉该节点下的所有dom,直接重新生成

image

给每个节点加key值比较就简单多了,不用像左图一样再去循环比对

2. 测试

通用测试框架 jest:https://jestjs.io/docs/zh-Hans/getting-started

react官方推荐:https://airbnb.io/enzyme/
首先安装安装

npm install enzyme-adapter-react-16 enzyme --save-dev

在package.json中加上test测试命令:

"test": "react-scripts test --env=jsdom"

具体用法可参考文档

3. PropTypes类型检查

proptypes类型检查工具:https://react.docschina.org/docs/typechecking-with-proptypes.html
简单的demo

Home.propTypes = {
  data: PropTypes.object.isRequired,
  actions: PropTypes.object.isRequired,
  history: PropTypes.object,
}

4. 引入antd组件并实现按需加载

安装ui组件: antd,安装babel-plugin-import可以实现antd按需加载

配置babal-plugin-import: https://blog.csdn.net/u010565037/article/details/86154544

5. react router

目前使用的是react-router-dom
参考文档:http://react-china.org/t/react-router4/15843

详细使用方法:https://www.jianshu.com/p/97e4af32811a

image
image
image
image

image
image
image
image

React-异步组件withRouter用法:
https://www.cnblogs.com/superlizhao/p/9497021.html
withRouter作用:
https://www.cnblogs.com/luowenshuai/p/9526341.html

6. createContext

Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
参考文档:https://react.docschina.org/docs/context.html

我们首先建一个AppContext.js文件

import React from 'react'
export const AppContext = React.createContext()

然后我们建一个createContext的高阶组件WithContext.js

import React from 'react'
import { AppContext } from './AppContext'

const withContext = (Component) => {
  return (props) => (
    <AppContext.Consumer>
      {({ state, actions }) => {
        return <Component {...props} data={state} actions={actions} />
      }}
    </AppContext.Consumer>
  )
}

export default withContext

在app.js中注册

<AppContext.Provider value={{
        state: this.state,
        actions: this.actions,
      }}>
      <Router>
        <div className="App">
          <div className="container pb-5">
            <Route path="/" exact component={Home} />
            <Route path="/create" component={Create} />
            <Route path="/edit/:id" component={Create} />
          </div>
        </div>
      </Router>
</AppContext.Provider>

7. redux

参考文档:https://www.redux.org.cn/
安装react-redux(作用是将顶层组件包裹在Provider组件之中,这样的话,所有组件就都可以在react-redux的控制之下了,但是store必须作为参数放到Provider组件中去)
详情参考:http://cn.redux.js.org/docs/react-redux/

react与react-redux: https://www.cnblogs.com/bax-life/p/8440326.html

来看一个简单的图例:


image

首先我们需要安装redux与react-redux

yarn add redux react-redux --save

redux的集成:
创建action模块
创建reducer模块
创建store模块
通过connect方法将react组件和redux连接起来
添加provider作为项目根组件,用于数据存储

redux调试工具安装

  1. 在chrome中安装Redux Devtools扩展
  2. 在项目中安装redux-devtools-extension

来看一个简单的redux例子:
新建store文件:创建index.js与reducer.js
reducer.js

const defaultState = {
    inputValue: '',
    list: []
}

export default (state = defaultState, action) => {
    if(action.type === "change_input_value") {
        const newState = JSON.parse(JSON.stringify(state));
        newState.inputValue = action.value;
        return newState;
    }
    if(action.type === "add_item") {
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(newState.inputValue);
        newState.inputValue = '';
        return newState;
    }
    return state;
}

index.js

import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer);

export default store;

app.js

import { Provider } from 'react-redux';
import store from './store';
const App = (
    <Provider store={store}>
        <TodoList />
    </Provider>
)

组件todolist.js

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

const TodoList = (props) => {
    const { inputValue, changeInputValue, handleClick, list } = props;
    return (
        <div>
          
        </div>  
    )
}

const mapStateToProps = (state) => {
    return {
        inputValue: state.inputValue,
        list: state.list
    }
}

// store.dispatch props
const mapDispatchToProps = (dispatch) => {
    return {
        changeInputValue(e) {
            const action = {
                type: "change_input_value",
                value: e.target.value
            }
            dispatch(action);
        },
        handleClick() {
            const action = {
                type: 'add_item'
            }
            dispatch(action);
        }
    }
}
// connect执行返回结果是一个容器组件
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

redux = reducer + flux
来看下图:


image

修改store组件->action->reducer->store

redux中间件:


image

注意事项:

  1. store是唯一的
  2. 只有store能够改变自己的内容
  3. reducer必须是纯函数
  4. reducer并没有改变store的内容,他生成了深拷贝,然后将值又返回给了store

开发中写法:
新建store文件夹,新建index.js与reducer.js文件
reducer.js

// 可以在每个组件中设置reducer,然后在总的reducer中引入它们
import { combineReducers } from 'redux-immutable'; // 创建combineReducers与Immutable.js状态一起使用的Redux的等效函数
import { reducer as headerReducer } from '../common/header/store';
import { reducer as homeReducer } from '../pages/home/store/';
import { reducer as detailReducer } from '../pages/detail/store/';
import { reducer as loginReducer } from '../pages/login/store/';
const redcuer = combineReducers({
    header: headerReducer,
    home: homeReducer,
    detail: detailReducer,
    login: loginReducer
});

export default redcuer;

index.js

// redux-devtools-extension 可视化工具
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'; // 实现ajax异步请求,使得支持action中为一个异步请求函数
import reducer from './reducer';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(reducer, composeEnhancers(
    applyMiddleware(thunk)
));

export default store;

这里以home中的为例,来看一个完整的过程
home文件夹store中的index.js

import reducer from './reducer';
import * as actionCreators from './actionCreators';
import * as actionTypes from './actionTypes';
export { reducer, actionCreators, actionTypes };

reducer.js

import { fromJS } from 'immutable';
import * as actionTypes from './actionTypes';
// immutable为了防止不小心改变state,所以用immutable(不可改变的数据)

const defaultState = fromJS({
    topicList: [],
    articleList: [],
    recommendList: [],
    articlePage: 1,
    showScroll: false
});

const changeHomeData = (state, action) => {
    return state.merge({
        topicList: action.topicList,
        articleList: action.articleList,
        recommendList: action.recommendList
    });
}

const addHomeList = (state, action) => {
    return state.merge({
        'articlePage': action.nextPage,
        'articleList': state.get('articleList').concat(action.list)
    });
}

export default (state = defaultState, action) => {
    switch(action.type) {
        case actionTypes.CHANGE_HOME_DATA:
            return changeHomeData(state, action);
        case actionTypes.ADD_HOME_LIST:
            return addHomeList(state, action);
        case actionTypes.TOGGLE_TOP_SHOW:
            return state.set('showScroll', action.show)
        default:
            return state;
    }
}

actionCreators.js

import * as actionTypes from './actionTypes';
import { fromJS } from 'immutable';
import axios from 'axios';

const addHomeList = (list, nextPage) => ({
    type: actionTypes.ADD_HOME_LIST,
    list: fromJS(list), // List方法,将普通数组转换为immutable数组
    nextPage
})

export const getMoreList = (page) => {
    return (dispatch) => {
        axios.get('/api/homeList.json?page='+page)
        .then((res) => {
            const result = res.data.data;
            dispatch(addHomeList(result, page + 1));
        })
    }
    
}

export const toggleTopShow = (show) => ({
    type: actionTypes.TOGGLE_TOP_SHOW,
    show
})

actionTypes.js

export const CHANGE_HOME_DATA = 'header/CHANGE_HOME_DATA';
export const ADD_HOME_LIST = 'header/ADD_HOME_LIST';
export const TOGGLE_TOP_SHOW = 'header/actionTypes.TOGGLE_TOP_SHOW';

组件index.js

class Home extends PureComponent { // PureComponent底层实现了shouldComponentUpdate,以防止state某些数据更新时所有页面都要更新, PureComponent最好用immutable,否则就不要用,用shouldComponentUpdate即可
}
const mapState = (state) => ({
    showScroll: state.getIn(['home', 'showScroll'])
})

const mapDispatch = (dispatch) => ({
    changeHomeData() {
        dispatch(actionCreators.getHomeInfo());
    },
    changeScrollTopShow() {
        if (document.documentElement.scrollTop > 100) {
            dispatch(actionCreators.toggleTopShow(true))
        } else {
            dispatch(actionCreators.toggleTopShow(false))
        }
    }
})

export default connect(mapState, mapDispatch)(Home);

8. React.Children的用法

https://blog.csdn.net/uuihoo/article/details/79710318

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容