react项目实战笔记

项目实战

1.脚手架生成项目

create-react-app jianshu

2.styled-components的使用

  • 1.安装styled-components包
yarn add styled-components
  • 2.创建style.js文件,代码如下
import { createGlobalStyle  } from "styled-components";
//导出全局样式
export const GlobalStyle = createGlobalStyle `
//此处是全局样式表,可以把reset.css放在这里
html, body, div, span{...}
`
  • 3.在index.js中引入,代码如下
import React from 'react';
import ReactDOM from 'react-dom';
//引入全局样式
import {GlobalStyle} from "./css/style.js";
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    /*全局样式*/
    <GlobalStyle />
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

3.在组建中使用styled-components

  • header组件的编写
    • header组件目录结构如下


      image.png
      • header (文件夹)
        • index.js (header组件的代码)
        • style.js (header组件的样式)
    • index.js代码如下
import react,{Component} from "react";
import {HeaderWrapper,NavLogo,Nav,NavLeft,NavRight,HeaderLink,LinkItem} from "./style"

class header extends Component{
    render(){
        return (
            <HeaderWrapper>
                <NavLogo href="/"></NavLogo>
                <Nav>
                    <NavLeft>
                        <p className="p1">首页</p>
                        <p className="p2">下载APP</p>
                        <input type="text" placeholder="搜索"/>
                    </NavLeft>
                    <NavRight>
                        <p>Aa</p>
                        <a href="">登录</a>
                    </NavRight>
                </Nav>
                <HeaderLink>
                    <LinkItem>注册</LinkItem>    
                    <LinkItem className="link2">写文章</LinkItem>    
                </HeaderLink>  
            </HeaderWrapper>
        )
    }
}
export default header;

style.js代码如下

import styled from "styled-components";
//引入图片
import logoImg from "../../asstes/img/nav-logo.png";
//HeaderWrapper会被解析成div元素 模板字符串中写样式,把HeaderWrapper暴露出去,在组件中使用就不会产生样式冲突问题了。
export const HeaderWrapper = styled.div`
  display: flex;
  height: 58px;
  padding: 0 39px;
  border-bottom:1px solid #eee;
`;
//NavLogo会被解析成a标签 注意图片要用import的方式
export const NavLogo = styled.a`
  background: url("${logoImg}") no-repeat;
`;
//NavLeft有子元素,子元素样式的写法
export const NavLeft = styled.div`
  NavLeft的样式
  .p1{
      p1的样式
  }
  .p2{
      p2的样式
  }
  input{
      input的样式
  }
`;
//其他代码省略....

4.使用redux改造代码

import  { Component } from "react";
import {connect} from "react-redux";

import {changeFocusStatus} from "../../store/actionCreator";
class header extends Component {
  constructor(props){
    super(props);
    this.state = {
      focus:false
    }
  }
  render() {
    const {props} = this;
    return (
      <HeaderWrapper>
        /*重点代码 其他代码省略*/
        <div className="search" className={props.foucsed==true?"search focus":"search"} >
            <input type="text" placeholder="搜索" onFocus={()=>{props.changeFoucs(true)}} onBlur={()=>{props.changeFoucs(false)}} />
            <span className="iconfont icon-fangdajing"></span>
         </div> 
      </HeaderWrapper>
    );
  }
}
const mapStateToProps = (state)=>{
  return {
    foucsed:state.foucsed
  }
}
const mapActionToProps = (dispatch)=>{
  return {
    changeFoucs(value){
      const action = changeFocusStatus(value);
      dispatch(action)
    }
  }
}
export default connect(mapStateToProps,mapActionToProps)(header);
5.拆分reducer.js,然后使用combineReducers进行组合,类似于vue的store拆分
  • reducer.js代码
import { combineReducers } from "redux";
import { reducer as HeaderReducer } from "../common/header/store";

const reducer = combineReducers({
    header:HeaderReducer
});

export default reducer;
  • 在header组件中创建store文件夹,下面存放actionCreators、reducer.js、actionTypes(更改文件名字为constant.js),之后在index.js中把三个文件引入统一对外暴露。目录结构如下图所示:


    image.png
  • index.js代码
import reducer from "./reduce";
import * as constant from "./constant";
import * as actionCreators from "./actionCreators";

export { reducer , constant , actionCreators }
  • reducer.js代码
import {CHANGE_FOUCS_STATUS} from "./constant";
const defaultStore = {
    foucsed:false
};
export default (store = defaultStore, action) => {
    if(action.type==CHANGE_FOUCS_STATUS){
        const newStore = JSON.parse(JSON.stringify(store));
        newStore.foucsed = action.value;
        return newStore;
    }
    return store;
};

constant.js(也就是actionTypes.js)代码 constant是常量的意思

export const CHANGE_FOUCS_STATUS = "change_foucs_status";

actionCreators.js代码

import { CHANGE_FOUCS_STATUS } from "./constant";

export const changeFocusStatus = (value) => {
  return {
    type: CHANGE_FOUCS_STATUS,
    value,
  };
};
  • 最后修改header组件index.js代码引入constant.js(也就是actionTypes.js)的方式
//其他代码省略
import { actionCreators } from "./store";
const mapActionToProps = (dispatch)=>{
  return {
    changeFoucs(value){
      const action = actionCreators.changeFocusStatus(value);
      dispatch(action)
    }
  }
}

5.使用immutable包保证state中的数据不被修改(防止写代码误操作)

  • 默认的数据在reducer.js中,改造reducer.js代码
import { CHANGE_FOUCS_STATUS } from "./constant";
//引入immutable库,mutable"可变的"的意思,immutable"不可变的"意思 
import { fromJS } from "immutable";
//通过fromJS方法传入一个对象,得到一个immutable对象
const defaultStore = fromJS({
  focused: false,
});
export default (store = defaultStore, action) => {
  if (action.type == CHANGE_FOUCS_STATUS) {
    // immutable对象的set方法,会结合之前immutable对象(也就是defaultStore)的值和要设置的值,返回一个全新的immutable对象,并不会对之前的对象做修改。
    return store.set("focused",action.value);
  }
  return store;
}; 
  • 由于store变成了一个immutable对象,所以获取值的方式也需要改变。
//header组件代码改动
const mapStateToProps = (state)=>{
  return {
    //由于
    focused:state.header.get("focused")
  }
}

6.在总的reducer.js中结合dedux-immutable库把最外层的reducer对象也变成immutable对象,改造代码如下:

// import { combineReducers } from "redux";
//引入包的时候combineReducers从redux-immutable引入就可以了。
import { combineReducers } from "redux-immutable";
import { reducer as HeaderReducer } from "../common/header/store";
const reducer = combineReducers({
    header:HeaderReducer
});
export default reducer;
  • 由于整个reducer返回的都是一个immutable对象,所以在页面中使用的时候都需要用get方法获取数据,改造header组件代码如下:
const mapStateToProps = (state)=>{
  return {
    // focused:state.get("header").get("focused")
    //下面的写法等价于上面的写法
    focused:state.getIn(["header","focused"])
  }
}

7.搜索框聚焦,请求搜索历史数据,使用redux-thunk中间件,把异步请求数据操作写在actionCreator中。效果如下:

image.png
  • store\index.js文件 加入中间件redux-thunk
import { createStore, compose, applyMiddleware } from "redux";
import reducer from "./reducer";
import thunk from "redux-thunk";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
    applyMiddleware(thunk)
));
export default store;
  • actionCreators.js 导出函数,在该函数中请求异步数据并通过dispatch方法传递action给store
//注意一个小细节,由于header中的store数据是调用fromJS得到的immutable数据,所以此处把拿到的数据也转换成immutable数据
import { fromJS } from "immutable";
const setKeywordList = (list) => ({
  type: KEYWORD_LIST,
  list:fromJS(list)
});
//暴露出去一个函数
export const getKeywordList = (data)=>{
  return async (dispatch)=>{
    const {data} = await axios.get("http://127.0.0.1:5500/data/headerList.json");
    const action = setKeywordList(data.list)
    dispatch(action);
  }
}
  • header\store\reduce.js 代码改造,改变if为switch
import { CHANGE_FOUCS_STATUS,KEYWORD_LIST } from "./constant";
//引入immutable库,mutable"可变的"的意思,immutable"不可变的"意思 
import { fromJS } from "immutable";
//通过fromJS方法传入一个对象,得到一个immutable对象
const defaultStore = fromJS({
  focused: false,
  list:["罗小黑","刺客伍六七"]
});
export default (store = defaultStore, action) => {
  switch(action.type){
    case CHANGE_FOUCS_STATUS:
      // immutable对象的set方法,会结合之前immutable对象的值和要设置的值,返回一个全新的对象。
      return store.set("focused",action.value); 
    case KEYWORD_LIST:
      return store.set("list",action.list);
    default:
      return store
  }
};
  • header\index.js 头部组件
import { Component } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";
class header extends Component {
  //渲染历史记录
  getHisTory(state, list) {
    if (state) {
      return (
        <History>
          {list.map((item) => {
            return (
              <div className="item" key={item}>
                <div className="item__left">
                  <i className="iconfont icon-clock"></i>
                  <span>{item}</span>
                </div>
                <i className="iconfont icon-chahao"></i>
              </div>
            );
          })}
        </History>
      );
    }
  }
  render() {
    const { focused,changeFoucs,list } = this.props;
    return (
      <NavLeft>
        <p className="p1">首页</p>
        <p className="p2">下载APP</p>
        <div className={focused == true ? "search focus" : "search"}>
          <input
            type="text"
            placeholder="搜索"
            onFocus={() => {
              changeFoucs(true);
            }}
            onBlur={() => {
              changeFoucs(false);
            }}
          />
          <span className="iconfont icon-fangdajing"></span>
          {this.getHisTory(focused, list)}
        </div>
      </NavLeft>
    );
  }
}
const mapStateToProps = (state) => {
  return {
    focused: state.getIn(["header", "focused"]),
    list: state.getIn(["header", "list"]),
  };
};
const mapActionToProps = (dispatch) => {
  return {
    changeFoucs(value) {
      const action = actionCreators.changeFocusStatus(value);
      dispatch(action);
      //如果聚焦,就去请求后端数据
      if(value)dispatch(actionCreators.getKeywordList())
    }
  };
};
export default connect(mapStateToProps, mapActionToProps)(header);

首页其他部分

  • 一共分为四个部分,效果图如下


    image.png
  • 每个部分可以将其拆分成组件去维护,首页只负责把其他组件添加进来。

  • 目录结构如下


    image.png
  • Recommond.js 组件代码如下

import { Component } from 'react'
import { Recommands, LoadMore } from '../style'
import { connect } from 'react-redux'
import { actionCreators } from '../store'
class Recommond extends Component {
  render() {
    let { list } = this.props
    return (
      <Recommands>
        {list.map((item, index) => {
          return (
            <div key={index} className="recommond__item">
              <div className="item__left">
                <h3>{item.get('title')}</h3>
                <p>{item.get('content')}</p>
              </div>
              <div className="item__right">
                <img src={item.get('img')} alt="" />
              </div>
            </div>
          )
        })}
        <LoadMore
          onClick={() => {
            this.props.loadMore(this.props.pageIndex)
          }}
        >
          阅读更多
        </LoadMore>
      </Recommands>
    )
  }
}
const mapStateToProps = (state) => {
  return {
    list: state.getIn(['home', 'recommondList']),
    pageIndex: state.getIn(['home', 'pageIndex']),
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    loadMore(pageIndex) {
      // console.log(actionCreators.loadMore())
      dispatch(actionCreators.loadMore(pageIndex))
    },
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(Recommond)
  • 如果想要在页面刚进入的时候就去请求home页面的数据,可以在componentDidMount的时候调用
componentDidMount() {
  //可以通过props找到映射到peops中的数据和方法
  this.props.getHomeData();
}
* 在使用图片作为背景图而图片是动态传入的话可以用以下写法
```jsx
//list.js代码如下
const List = (props) => {
  let { list } = props
  return (
    <div>
      {list.map((item, index) => {
        return <ListsItem imgUrl={item} key={index} />
      })}
    </div>
  )
}
//style.js代码如下 ${} 里面可以写一个函数,函数的形参就是一个对象 通过对象.属性名可以访问到值
export const ListsItem = styled.div`
  width: 280px;
  height: 50px;
  background: url(${(props) => props.imgUrl});
  background-size: cover;
`
  • 使用PureComponent优化性能
    • 父组件中的state和props中的数据发生变化的时候,render会重新执行,并且子组件的render也会被执行,但有时候子组件不需要重新渲染,此时可以用shouldComponentUpdate加入判断逻辑决定子组件是否要重新渲染,PureComponent实现了shouldComponentUpdate,但最好结合immutable一起使用,不然可能会出错。
      项目地址:https://gitee.com/yyagami/jianshu.git
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容