简书项目----总结

2020-06-01  本文已影响0人  学的会的前端

1. 项目搭建

  1. 使用create-react-app搭建项目
npm install -g create-react-app
create-react-app jianshu
  1. 关于css

一个.css文件一旦被引入后,是在全局生效的。只要css类一致,样式都一致,就会造成两个css文件的相互冲突。所以引入第三方模块styled-components,对样式进行统一管理,样式写在JS中。

yarn add styled-components
import "./style.js"

2. 动画效果实现

在react中,不要直接操作DOM,二叔操作数据,数据改变,页面自动更新渲染。

  1. 安装react-transition-group
npm install react-transition-group // yarn add react-transition-group
  1. 使用
import {CSSTransition} from "react-transition-group"
  1. 属性
  1. 代码实现
<CSStransition in = {this.state.show} timeout = {2000} classNames = "fade" appear = {true}>
  <div>hello</div>
</CSStransition>
//入场动画  从无到有
.fade-enter .fade-appear{opacity: 0}
.fade-enter-active .fade-appear-active{opacity: 1; transition: opacity 1s ease-in}
.fade-enter-done{opacity: 1}
//出场动画  从有到无
.fade-exit{opacity:1}
.fade-exit-active{opacity: 0; transition: opacity:0 1s ease-in}
.fade-exit-done{opacity: 0}
  1. 针对多个DON元素的动画切换
import {CSSTransition,TransitionGroup} from "react-transition-group"

循环外部使用<TransitionGroup>,内部具体元素使用<CSSTransition><CSSTransition>中只能有一个元素。

3. 使用react-redux进行数据管理

  1. 安装
yarn add redux //通过store对数据进行管理
yarn add react-redux // 方便在react中使用redux
  1. 使用
import {createStore} from "redux"
import reducer from reducer.js
const store = createStore(store)
export default store
import {Provider} from "react-redux"
import store  from "./store/index.js"
//Provider这个提供器连接了store
//Provider下的所有子组件都可以对store中的数据进行获取及修改
<Provider store = {store}>
  <Header/>
</Provider>
import {connect} from "react-redux"
export default commect(mapState,mapDispatch)(Header)

mapState,mapDispatch是一个函数返回一个对象,前者是获取到store中的数据,后者是派发action,对store中的数据进行修改。组件通过this.props使用相应的数据.

const mapState = (store) => ({
//没有react-redux的时候,需要使用store.getState()进行数据获取,
//现在使用connect连接了store,可以直接获取到数据
  data: store.data   //使用时this.props.data
})
const mapDispatch = (dispatch) => {
  return {
    handleChangeData(e){
      const action = {
        type: "change_data"
        data: e.target.value
      }
      dispatch(action)
    }
  }
}
//组件中使用this.props.handleChangeData()
//定义初始化数据
const defaultState = {
  data: ''
}
export default (state=defaultState,action) => {
  if(action.type ==="change_data" ){
    //store中的数据不能直接修改,需要深拷贝一份进行修改
    const newState = JSON.parse(JSON.Stringify(state))
    newState.data = action.data
    return newState
  }
  return state
}

4. 使用combineReducers完成对数据的拆分管理

现在所有的数据都是放在reducer.js中,数据的维护也放在reducer.js中,代码过于繁琐,难以维护。combineReducers可以吧reducer拆分成几个小的reducer进行管理。
store只有一个,但是reducer可以拆分成几个

import {combineReducers} from "redux"
import { combineReducers } from "redux";
import { reducer as headerReducer } from "../common/header/store";
const reducer = combineReducers({
  header: headerReducer,
});
export default reducer;

注意点: 使用这种方式,数据外部就多了一层包裹header,所以在mapState函数中获取数据时,采用的是:data: state.header.data

5. 代码优化

将store下的所有文件引入到index.js,之后在导出,在其他文件中,直接引入index.js即可。那么其他文件的接口也都暴露出来了。

import reducer from "./reducer.js";
import * as constants from "./constants.js";
import * as actionCreators from "./actionCreators.js";
export { reducer, constants, actionCreators };

6. 使用immutable.js来管理store中的数据

reducer中顶一个初始化数据,也定义了对数据的操作,reducer接受原始的state,一定不能对原始的state进行修改,要去state进行一次深拷贝,修改state并返回.
Immmutable.js是一个第三方模块,帮助我们生成Immutable对象(不可改变的对象)。

  1. 安装
npm install immutable  // yarn install immutable
  1. 使用
//在分reducer.js文件中
import {fromJS} from "immutable"
const defaultState = fromJS({
  data: false
})
//此时state是immutable对象
const mapState = (state) => ({
  data: state.get("header").get("data")   //  二者相同  data: state.getIn(["header","data"])
})
//state.get("header")是因为大的reducer生成的state是Immutable对象了,所以使用get获取header 。
//get("header").get("data")自身的state已经变成immutable对象,所以使用get 获取data
export default (state=defaultState,action) => {
  switch(action.type){
    case "change_data" :
      //一定要保证数据类型的统一
      return state.set("data",fromJS(action.data))
    default:
      return state
  }
}

immutableset()方法结合之前的immutable对象的值,和设置的值,返回一个全新的对象,并没有修改之前的immutable数据

yarn add redux-immutable

使用,使生成的reducer 是immutable对象。

import {combineReducers} from "redux-immutable"

7. ajax获取数据

获取ajax是异步操作,借助于redux-thunk。他是redux 的中间件,指的是action和store之间,在redux当中,action只能是一个对象,使用dispatch派发给store,但使用了redux-thunk之后,action可以是一个函数

  1. 安装
yarn add redux-thunk
  1. 对store进行修改
import { createStore, compose, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer.js";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
  1. 发送ajax借助于第三方工具,axios。
yarn add axios
const mapDsiaptch = (dispatch) => {
  handleChangeData(){
    dispatch(actionCreators.getList())
  }
}
import axios from "axios"
export const getList = () => {
  return (dispatch) => {
    axios.get("/api/headerList.json")
      .then((res) => {
        const data = res.data;
        //data.data是js对象
        //获取到数据之后,修改数据,需要再次派发action
        dispatch(changeList(data.data));
      })
      .catch(() => {
        console.log("error");
      });
  };
};

const changeList = (data) => ({
  type: constants.CHANGE_LIST,
  //接受到的data是js对象,而reducer中处理的是immutable对象,所以需要对data进行数据转换
  data: fromJS(data),
});
if(action.type === constants.CHANGE_LIST){
  //采用immutable对象的set方法对数据进行修改,创建了新的数据List,并没有修改原数据list。
  return state.set("list",action.data)
}
if(list.size===0){
  dispatch(actionCreators.getList())
}

以上代码等价于

(list.size===0) &&  dispatch(actionCreators.getList())

8. 采用DOM方式实现动画旋转效果

.spin {
  display: block;
  float: left;
  transition: all 1s ease-in;
  ttransform-origin: center center; //以中心为旋转点
}
<i ref = {(icon) => {this.spinIcon = icon}}
//原生Js获取到rotate的方法
spin.style.transform
//使用正则获取到rotate的值,获取到的值是字符串
spin.style.transform.replace(/[^0-9]/ig,'')
<SearchSwitch onClick = {() => handleChange(this.spinIcon)}
let originAngle = spin.style.transform.replace(/[^0-9]/ig,'')
if(originAngle){
  originAngle = parseInt(originAngle,10)
}else{
  originAngle = 0
}
spin.style.transform = "rotate" + (originAngle + 360) + "deg"

9. 路由

yarn add react-router-dom
//页面入口文件App.js
import {BrowserRouter,Route} from "react-router-dom"
<BrowserRouter>
  <div>
    <Route path = "/" exact component = {Header} />
  </div>
</BrowserRouter>

BrowserRouter:代表的是路由,里面只能有一个直接子元素,表示BrowserRouter内所有内容都将使用路由.
Route:代表一个个路由规则。
exact:强制路由完全匹配,才会显示相应的内容。
component:路由相匹配,显示组件的内容。
path:路径。

10.immutable的三个方法

state.merge({
  data: action.data.
  list: action.list
})
state.getIn(["header","list"])
state.set("data",fromJS(action.data))

11.实现加载更多

// List.js

<div className="loadMore" onClick={() => getMoreList(page)}>
          加载更多
 </div>
const mapState = (state) => ({
  list: state.getIn(["home", "articleList"]),
  page: state.getIn(["home", "articlePage"]),
});
const mapDispatch = (dispatch) => ({
  getMoreList(page) {
    const action = actionCreators.getMoreList(page);
    dispatch(action);
  },
});

// actionCreatore.js

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));
      })
      .catch(() => {
        alert("error");
      });
  };
};

//reducer.js

const defaultState = {articlePage = 1}
const getMoreList = (state, action) => {
  return state.merge({
    articleList: state.get("articleList").concat(action.list),
    aticlePage: action.newPage,
  });
};
 case constants.GET_MORE_LIST:
      return getMoreList(state, action);

12.回到顶部

<button onClick = {this.handleScrollTop.bind(this)}>回到顶部</button>
handleScrollTop(){
  window.scrollTo(0,0)  //即实现了回到顶部
}
//通过控制showScroll实现button的显示和隐藏
{this.props.showScroll ? <button> : ""}

//在store中定义showScroll,来控制Button的显示和隐藏
componentDidMount(){this.bindEvents()}
bindEvents(){
  window.addEventListener("scroll",this.props.changeScrollTopShow())
}
//一定要在组件解绑的时候,移除时间绑定,防止对其他组件造成影响
componentWillUnmout(){
  window.removeEventListener("scroll",this.props.changeScrollTopShow())
}
// 通过片段滚动的高度,来判断是否处在第一页,分别派发action,传递不同的参数
const mapDispatch = (dispatch) => ({
  changeScrollTopShow() {
    if (document.documentElement.scrollTop > 300) {
      dispatch(actionCreators.topShow(true));
    } else {
      dispatch(actionCreators.topShow(false));
    }
  },
});

13.组件性能优化

  1. 组件重渲染优化
    组件的数组都放在同一个store中,那么一个数据变化,所有的组件都会自动调用render()函数,对组件进行重新渲染,很浪费性能。调用组件的shouldComponentUpdate(),可以进行判断,当自身组件依赖的数据改变时,这个组件才进行重渲染,但是在每个组件中写一次这个方法,很麻烦,react提供了PureComponent组件,这个组件底层实现了shouldComponentUpdate(),可以保证自身依赖数据变化,组件菜进行重渲染.
import {PureComponent} from "react"

一般,PureComponent组件immutable数据管理结合使用。

  1. 路由跳转优化
    在react中实现路由跳转的时候,要使用React-Router-Dom,这个第三方模块,这个模块的跳转是单页面跳转。不管怎么跳转,页面只会加载一次HTTP文件(HTML文件)。使用a标签实现的跳转,每跳转一次就会加载一次HTTP文件,所以不能使用a标签,React-Router-Dom提供了Link实现页面的跳转.
import {Link} from "react-router-dom"
<Link to = "/"> //跳转到Header组件,减少了HTTP请求

14.页面路由参数传递

  1. 动态路由获取参数
    点击跳转的时候,把id也传给它
<Link to = {'/detail/' + item.get("id")}/>
  1. 在路由配置的时候,访问detail路径,还需要传递一个额外的id参数
<Route path = "/detail/:id" exact component = {Detail}/>
  1. 在Detail组件中取到id的值,发送ajax请求接口时,把id传给后端
this.props.match.params.id
componentDidmount(){
  this.props.getDetail(this.props.match.params.id)
}
//在发送ajax请求时,传入id
mapDispatch = (dispatch) => {
  getDetail(id){
    dispatch(actionCreatores.getDetail(id))
  }
}
//创建action
export const getDetail = (id) => {
  return (dispatch) => {
    axios.get("/api/detail/json?id = " + id).then().catch()
  }
}

15.登录功能实现

  1. 功能:未登录,点击跳转登录页面,登陆成功,跳转到首页,并且可以跳转写文章页面,还可以点击退出登录。
  2. 登录部分代码实现
//  登录按钮部分代码
 {login ? (
            <NavItem className="right" onClick={logout}>
              退出
            </NavItem>
          ) : (
      //用户点击t跳转到登录页面
            <Link to="/login">
              <NavItem className="right">登录</NavItem>
            </Link>
 )}
//获取到状态数据
const mapStateToProps = (state) => {
  return {
    //所有的数据都是Immutable对象,所以采用immutable的方式获取到
    login: state.getIn(["login", "login"]),
  };
};
//点击退出,发送ajax
const mapDispatchToProps = (dispatch) => {
  return {
    logout() {
      dispatch(loginActionCreators.logout());
    },
  };
};

  1. Login登录组件代码实现,实现登录陈宫跳转到首页
import React, { PureComponent } from "react";
import "./style.css";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import { actionCreators } from "./store/index.js";
class Login extends PureComponent {
  render() {
    const { loginStatus } = this.props;
    if (!loginStatus) {
      return (
        <div className="loginWrapper">
          <div className="loginBox">
            <input
              className="loginInput"
              placeholder="用户名"
              ref={(input) => (this.account = input)}
            ></input>
            <input
              className="loginInput"
              placeholder="密码"
              type="password"
              ref={(input) => (this.password = input)}
            ></input>
            <div
              className="loginBtn"
//点击登录实际是发送ajax请求,把用户名和密码一起发送到后端
              onClick={() => this.props.login(this.account, this.password)}
            >
              提交
            </div>
          </div>
        </div>
      );
    } else {
      //重定向到首页
      return <Redirect to="/" />;
    }
  }
}
const mapState = (state) => ({
  loginStatus: state.getIn(["login", "login"]),
});
const mapDispatch = (dispatch) => ({
  login(accountElement, passwordElement) {
    console.log(accountElement.value, passwordElement.value);
    dispatch(actionCreators.login(accountElement.value, passwordElement.value));
  },
});
export default connect(mapState, mapDispatch)(Login);
import axios from "axios";
import * as constants from "./constants";
import { fromJS } from "immutable";
const changeLogin = () => ({
  type: constants.CHANGE_LOGIN,
  value: true,
});
export const login = (account, password) => {
  return (dispatch) => {
    axios
      .get("/api/login.json?account=" + account + "&password= " + password)
      .then((res) => {
        console.log(res);
        const result = res.data.data;
        console.log(result);
        if (result) {
          dispatch(changeLogin());
        } else {
          console.log("error");
        }
      });
  };
};

//点击退出,修改的是login中的数据,所以action派发给login的reducer,但是是在Header
//组件中对Login中的数据提出的修改请求
export const logout = () => ({
  type: constants.CHANGE_LOGOUT,
  value: false,
});

import { fromJS } from "immutable";
import * as constants from "./constants.js";
const defaultStore = fromJS({
  login: false,
});

export default (state = defaultStore, action) => {
  //if语句 使用switch--case代替
  switch (action.type) {
    case constants.CHANGE_LOGIN:
      return state.set("login", action.value);
    case constants.CHANGE_LOGOUT:
      return state.set("login", action.value);
    default:
      return state;
  }
};
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";

class Write extends PureComponent {
  render() {
    const { loginStatus } = this.props;
    if (loginStatus) {
      return <div>写文章页面</div>;
    } else {
      //重定向到首页
      return <Redirect to="/login" />;
    }
  }
}
const mapState = (state) => ({
  loginStatus: state.getIn(["login", "login"]),
});

export default connect(mapState, null)(Write);

<Link to="/write">
            <Button className="writting">
              <i className="iconfont">&#xe6a6;</i>
              写文章
            </Button>
</Link>
<Route path="/write" exact component={Write}></Route>

16. 异步组件

路由切换过程中,美誉加载其他任何JS组件,说明所有页面对应的所有组件的代码都在一个文件中,访问首页和其他页面也一起进行加载,比较浪费资源,性能比较差。使用异步组件可以实现,加载当前页面的时候,仅加载当前页。
异步组件的底层比较复杂,但是使用封装好的第三方模块还是比较简单。

yarn add react-loadable
import Loadable from "react-loadable";
import React from "react"

const LoadableComponent = Loadable({
  loader: () => import("./"),  //import指的是异步加载新语法
  loading () {
    return <div>正在加载</div>
  },
});
export default () => <LoadableComponent />;
import Detail from "./common/detail/loadable.js";
//访问的是loadable.js
<Route path="/detail/:id" exact component={Detail}></Route>
上一篇 下一篇

猜你喜欢

热点阅读