Alan

此刻想举重若轻,之前必要负重前行

总结学习React以及相关插件时遇到的难点以及重点

基础知识点

  • 单向数据流(子组件不能修改父组件数据)

  • 组件名首字母大写

  • 可以使用<React.Fragment></ React.Fragment>和<></>包裹标签

  • 关键字

    • class->className
    • <label for="id名"></lable>-><label htmlFor="id名"></lable>用来扩大点击范围
  • 不要直接修改state要使用this.setState()

  • this指向问题

    // 在constructor使用bind,无法传参数
    this.handlerClick = this.handlerClick.bind(this);
    // 调用时使用bind,对性能优化不友好,因为每次render()时都要重新bind
    onChange={this.handlerClick.bind(this,num)}
    // 使用箭头函数
    onChange={e => this.handlerClick(num)}
    // 声明为箭头函数,无法传参
    handlerClick = (num) => {
    };
  • 循环渲染加key,不要使用index作为key值。

  • 注释写法{/**/}

  • dangerouslySetInnerHTML={{__html: item(需要展示的数据)}} // 不转义html标签
  • setState()第二个参数为回调函数

  • 一旦state、props变化,render()就会执行。也就是说一旦父组件state变化时,render()会执行所以其父组件的中的子组件也会再render()一遍。

  • 动画组件react-transition-group

  • 当组件中只有render()时,可以把它声明成一个无状态组件,可以提升性能

  • ref用来获取dom元素,ref={(element) => {}}element为该元素

组件通信

父->子

通过属性传递,子组件通过this.props接受,父组件值的改变会直接影响到子组件。

父组件

// 父组件中使用子组件
const name = 'Alan'

<Child name={name} />

子组件

<div>{this.props.name}</div>

子->父亲

父组件将自己方法传递给子组件,子组件通过添加事件来调用该方法。这样就可以达到子组件修改父组件数据的目的,同时可以将子组件的数据传递给父组件。

父组件

this.state = {
    list: [1,2,3]
}

<Child handlerEvent={this.deleteItem.bind(this)} />

// 方法
deleteItem(index) {
    const list = [...this.state.list];
    list.splice(index,1);
    this.setState({
        list,
    });
}

子组件

<button onClick = {() => this.props.handlerEvent(1)}></button>

props参数校验及默认值

具体参数

import PropTypes from 'prop-types';

// 参数校验
// Child为组件名
// 定义this.props.content为string类型并且为必须参数
Child.propTypes = {
  content: PropTypes.string.isRequired,
};

// 参数默认值
Child.defaultProps = {
  mobile: 'none',
};

异步组件加载插件react-loadable

使用方式

import React, { Component } from 'react';
import Loadable from 'react-loadable';

const LoadableComponent = Loadable({
  // 需要异步引入的组件
  loader: () => import('./index'),
  loading() {
    // 加载时进行的操作,这里显示loading提升用户体验
    return <div>loading...</div>;
  },
});

export default class App extends Component {
  render() {
    return <LoadableComponent />;
  }
}

虚拟DOM

何为虚拟DOM

使用JS对象来描述真实DOM,虚拟DOM(JS对象)的操作性能要远远优于真实DOM操作性能。

通过React.createElement(type, [props], [...children])来生成虚拟DOM

优点:

  • DOM操作很耗性能,虚拟DOM操作性能好

  • 无需替换全部DOM,通过diff算法比对替换局部改变的DOM

  • 由于使用了虚拟DOM,有利于原生应用的开发(RN),因为DOM是存在于浏览器中的。

出于性能考虑,react将多次setState(异步函数)合并成一次setState,因为setState会导致虚拟DOM进行diff对比。

同层比对

diff算法

生命周期

  • mounting
    • componentWillMount()/UNSAFE_componentWillMount()
    • render()
    • componentDidMount()应用场景:发送请求
  • updation(props/state发生变化)
    • componentWillReceiveProps()将废除
    • shouldComponentUpdate(): boolean更新之前,返回true时才会更新
    • render()
    • componentDidUpdate()
  • unmounting
    • componentWillUnmount()

性能优化

// 只有当子组件数据变化时才去执行render()
shouldComponentUpdate(nextProps, nextState) {
    return nextProps.content !== this.props.content;
    // 防止更新影响性能
}

也可以使用组件去继承React.PureComponent以达到和上面一样的效果,但是应该减少使用,因为它存在一些问题。问题

在hook中也有类似的API:useMemo和useCallback

useMemo参考资料:

How to useMemo in React

可以使用Charles实现接口mock。localhost->本地文件.json

Redux

image-20200622095821817

思路:

组件通过dispatch(action)来通知store,store根据action来调用对应的reducers来操作store副本,reducers将处理好的数据返回给store。

store和组件之间通过,store.subscribe(this.setState(store.getState()))来同步最新的store数据。

  1. 创建store(createStore())
import { createStore } from "redux";
import reducer from "./reducer";
// 第二个参数配置后可以使用谷歌redux插件
const store = createStore(
  reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
  1. 创建reducer(管理操作数据store副本)
const defaultState = {
  todoText: ''
};

export default (state = defaultState, action) => {
  if (action.type === 'action的type') {
    let newState = JSON.parse(JSON.stringify(state));
    // 更新store
    newState.todoText = action.value;
    return newState;
  }
  return state;
}

可以通过store.getState()来获取store中的数据,给组件赋初值

this.state = store.getState();
  1. 创建action
const action = {
    type: '',
    value: '' // 要改变的值
}
  1. 通过store.dispatch(action)通知store,reducer将接收到修改之前的state和action
  2. reducer更新state的副本,将新state返回给store
  3. 使用store的组件中通过store.subscribe(this.setState(store.getState()))订阅store来同步最新的store

为了提高代码健壮性和可维护性,把action单独声明为一个文件,并且把action.type声明为一个常量文件。

store
 ├── actionCreators.js // 生成action
 ├── actionType.js // action type对应的常量
 ├── index.js
 └── reducer.js
import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
import { INPUT_CHANGE } from './actionType';

export const changeInputAction = (value) => ({
  type: INPUT_CHANGE,
  value,
});
import { INPUT_CHANGE } from './actionType';

const defaultState = {
  todoText: '',
};

export default (state = defaultState, action) => {
  if (action.type === INPUT_CHANGE) {
    let newState = JSON.parse(JSON.stringify(state));
    newState.todoText = action.value;
    return newState;
  }
  return state;
}
export const INPUT_CHANGE = 'inputChange';

reducers函数的返回值要是可预测,也就是说里面不能写类似new Date

reducers不能修改原来的state,只能返回一个新的state,为了防止被误改造成bug,可以使用immutable.js来解决,它可以将state数据转化成一个特定的对象

npm i immutable redux-immutable -S
  • immutable对象通过get()set()等api来操作数据,当有多个set()连用时可以使用merge({})来实现
  • immutable通过fromJS可以将state转化成immutable对象,通过toJS()将immutable对象转化成js对象
  • redux-immutable也提供了combineReducers,结合不同模块的reducers的同时还可以将state转化成immutable对象。

当项目越来越大时,项目中的reducer.js文件也会越来越臃肿,这个时候我们可以通过redux提供的combineReducers来将reducer拆分成不同的模块

import { combineReducers } from 'redux';
import mAReducer from '../mAReducer/store/reducer';
import mBReducer from '../mBReducer/store/reducer';

export default combineReducers({
  A: mAReducer,
  B: mBReducer,
});
// 这样我们在使用mAReducer中的数据是就要通过state.A.xxx来获取其数据了

redux-thunk

中间件:位于action和store之间

redux-thunk可以让redux使用异步操作

使用方式

import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
  : compose;

const enhancer = composeEnhancers(applyMiddleware(thunk));

const store = createStore(reducer, enhancer);

export default store;

例子

export const setTodoList = (list) => ({
  type: GET_TODOLIST,
  list,
});

export const getTodoList = () => {
  return (dispatch) => {
    axios.get('http://localhost:8888/test/getTodoList')
    .then((res) => {
      const todoList = res.data.datas;
      const action = setTodoList(todoList);
      dispatch(action);
    });
  }
};
componentDidMount() {
    const action = getTodoList();
    store.dispatch(action);
}

redux-saga

使用方式

import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
import mySaga from './sagas';

const sagaMiddleware = createSagaMiddleware();

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
  : compose;

const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware));
const store = createStore(reducer, enhancer);
sagaMiddleware.run(mySaga);

export default store;
export const getSaga = (list) => ({
  type: GET_SAGA,
  list,
});
import { takeEvery } from 'redux-saga/effects';
import { GET_SAGA } from './actionType';

function* getList() {
  console.log('进行异步操作了');
}

function* mySaga(action) {
  // 当dispatch的action.type = GET_SAGA时,调用getList方法
  yield takeEvery(GET_SAGA, getList);
}

export default mySaga;

react-redux

使用Providerconnect实现store的共享,之前的写法是需要在需要使用store的组件中引入store,并通过store.subscribestore.getState来同步和获取最新的store内容。

<Provider store={store}>
    {/* 包裹需要使用store的组件 */}
    <App />
</Provider>

Demo.js

import { deleteTodoItemAction } from '../../store/actionCreators';

// 将store中的state的数据映射到组件的props中
const mapStateToProps = (state) => ({
  ...state,
});

// 将dispatch映射到props中
const mapDispatchToProps = (dispatch) => {
  return {
    deleteTodoItem(index) {
      const action = deleteTodoItemAction(index);
      dispatch(action);
    }
  };
};


// Demo为需要使用store的组件
connect(mapStateToProps, mapDispatchToProps)(Demo);

hook

如果使用的是hook进行开发的话,可以使用react-redux提供的hookapi来简化书写,并且不需要使用connect()来包裹组件,但是仍然需要使用Provider包裹父组件

import { useSelector, useDispatch } from 'react-redux'
import { deleteTodoItemAction } from '../../store/actionCreators';

const counter = useSelector(state => state.counter)

const dispatch = useDispatch()

// 使用
dispatch(deleteTodoItemAction)

css写法

react不像vue那样可以优雅的在vue文件中写css代码,不过react也提供了几种css书写方式(待补充)

  • css in js
  • 使用styled-components
  • css-module(依赖于webpack)

styled-components(使用js编写css代码)

import styled from 'styled-components';

export const Logo = styled.a.attrs({
  href: '/',
})`
  position: absolute;
  top: 0;
  left: 0;
  background: url(${(props) => props.imgUrl});
`;
import React, { Component } from 'react';
import { Logo } from './style';

class Header extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    return (
        <Logo imgUrl="xxx"/>
    );
  }
}

export default Header;

上面这种写法等同于

render() {
    return (
        <a href="/" style={{position: "absolute", top: 0, left: 0}}></a>
    );
}

react-router

npm i react-router-dom -S

使用方式

import React from 'react';
import Header from './common/header';
import store from './store';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import Home from './pages/home';
import Detail from './pages/detail';

function App() {
  return (
    <Provider store={store}>
      <Router>
        <Header />
        <Route path="/" exact component={Home}></Route>
        <Route path="/detail" exact component={Detail}></Route>
      </Router>
    </Provider>
  );
}

export default App;

exact为精准匹配路由

页面跳转,使用react-router-dom中的<Link to="xxx"></Link>,重定向使用<Redirect to="xxx"></Redirect>

编程式写法:

this.props.history.(push()/goBack(num)/go()/replace())

动态路由

<Route path="/post/:id" exact component={POST}></Route>
// POST组件通过this.props.match.params.id来获取id值

可以使用withRouter包裹组件来获取history

总结

React对比Vue来说,学习成本比较高,但是比较灵活。而vue提供了很多封装好的api,学习起来对小白比较友好。

评论