項(xiàng)目實(shí)戰(zhàn)
1.腳手架生成項(xiàng)目
create-react-app jianshu
2.styled-components的使用
- 1.安裝styled-components包
yarn add styled-components
- 2.創(chuàng)建style.js文件,代碼如下
import { createGlobalStyle } from "styled-components";
//導(dǎo)出全局樣式
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組件目錄結(jié)構(gòu)如下
image.png- header (文件夾)
- index.js (header組件的代碼)
- style.js (header組件的樣式)
- 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">首頁(yè)</p>
<p className="p2">下載APP</p>
<input type="text" placeholder="搜索"/>
</NavLeft>
<NavRight>
<p>Aa</p>
<a href="">登錄</a>
</NavRight>
</Nav>
<HeaderLink>
<LinkItem>注冊(cè)</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會(huì)被解析成div元素 模板字符串中寫樣式,把HeaderWrapper暴露出去,在組件中使用就不會(huì)產(chǎn)生樣式?jīng)_突問(wèn)題了宙刘。
export const HeaderWrapper = styled.div`
display: flex;
height: 58px;
padding: 0 39px;
border-bottom:1px solid #eee;
`;
//NavLogo會(huì)被解析成a標(biāo)簽 注意圖片要用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>
/*重點(diǎn)代碼 其他代碼省略*/
<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進(jìn)行組合,類似于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組件中創(chuàng)建store文件夾,下面存放actionCreators矿酵、reducer.js瓷患、actionTypes(更改文件名字為constant.js),之后在index.js中把三個(gè)文件引入統(tǒng)一對(duì)外暴露描沟。目錄結(jié)構(gòu)如下圖所示:
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中的數(shù)據(jù)不被修改(防止寫代碼誤操作)
- 默認(rèn)的數(shù)據(jù)在reducer.js中,改造reducer.js代碼
import { CHANGE_FOUCS_STATUS } from "./constant";
//引入immutable庫(kù),mutable"可變的"的意思,immutable"不可變的"意思
import { fromJS } from "immutable";
//通過(guò)fromJS方法傳入一個(gè)對(duì)象,得到一個(gè)immutable對(duì)象
const defaultStore = fromJS({
focused: false,
});
export default (store = defaultStore, action) => {
if (action.type == CHANGE_FOUCS_STATUS) {
// immutable對(duì)象的set方法,會(huì)結(jié)合之前immutable對(duì)象(也就是defaultStore)的值和要設(shè)置的值,返回一個(gè)全新的immutable對(duì)象,并不會(huì)對(duì)之前的對(duì)象做修改。
return store.set("focused",action.value);
}
return store;
};
- 由于store變成了一個(gè)immutable對(duì)象,所以獲取值的方式也需要改變。
//header組件代碼改動(dòng)
const mapStateToProps = (state)=>{
return {
//由于
focused:state.header.get("focused")
}
}
6.在總的reducer.js中結(jié)合dedux-immutable庫(kù)把最外層的reducer對(duì)象也變成immutable對(duì)象,改造代碼如下:
// import { combineReducers } from "redux";
//引入包的時(shí)候combineReducers從redux-immutable引入就可以了。
import { combineReducers } from "redux-immutable";
import { reducer as HeaderReducer } from "../common/header/store";
const reducer = combineReducers({
header:HeaderReducer
});
export default reducer;
- 由于整個(gè)reducer返回的都是一個(gè)immutable對(duì)象,所以在頁(yè)面中使用的時(shí)候都需要用get方法獲取數(shù)據(jù),改造header組件代碼如下:
const mapStateToProps = (state)=>{
return {
// focused:state.get("header").get("focused")
//下面的寫法等價(jià)于上面的寫法
focused:state.getIn(["header","focused"])
}
}
7.搜索框聚焦,請(qǐng)求搜索歷史數(shù)據(jù),使用redux-thunk中間件,把異步請(qǐng)求數(shù)據(jù)操作寫在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 導(dǎo)出函數(shù),在該函數(shù)中請(qǐng)求異步數(shù)據(jù)并通過(guò)dispatch方法傳遞action給store
//注意一個(gè)小細(xì)節(jié),由于header中的store數(shù)據(jù)是調(diào)用fromJS得到的immutable數(shù)據(jù),所以此處把拿到的數(shù)據(jù)也轉(zhuǎn)換成immutable數(shù)據(jù)
import { fromJS } from "immutable";
const setKeywordList = (list) => ({
type: KEYWORD_LIST,
list:fromJS(list)
});
//暴露出去一個(gè)函數(shù)
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庫(kù),mutable"可變的"的意思,immutable"不可變的"意思
import { fromJS } from "immutable";
//通過(guò)fromJS方法傳入一個(gè)對(duì)象,得到一個(gè)immutable對(duì)象
const defaultStore = fromJS({
focused: false,
list:["羅小黑","刺客伍六七"]
});
export default (store = defaultStore, action) => {
switch(action.type){
case CHANGE_FOUCS_STATUS:
// immutable對(duì)象的set方法,會(huì)結(jié)合之前immutable對(duì)象的值和要設(shè)置的值,返回一個(gè)全新的對(duì)象礼预。
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">首頁(yè)</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);
//如果聚焦,就去請(qǐng)求后端數(shù)據(jù)
if(value)dispatch(actionCreators.getKeywordList())
}
};
};
export default connect(mapStateToProps, mapActionToProps)(header);
首頁(yè)其他部分
-
一共分為四個(gè)部分,效果圖如下
image.png 每個(gè)部分可以將其拆分成組件去維護(hù),首頁(yè)只負(fù)責(zé)把其他組件添加進(jìn)來(lái)。
-
目錄結(jié)構(gòu)如下
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)
- 如果想要在頁(yè)面剛進(jìn)入的時(shí)候就去請(qǐng)求home頁(yè)面的數(shù)據(jù)抹锄,可以在componentDidMount的時(shí)候調(diào)用
componentDidMount() {
//可以通過(guò)props找到映射到peops中的數(shù)據(jù)和方法
this.props.getHomeData();
}
* 在使用圖片作為背景圖而圖片是動(dòng)態(tài)傳入的話可以用以下寫法
```jsx
//list.js代碼如下
const List = (props) => {
let { list } = props
return (
<div>
{list.map((item, index) => {
return <ListsItem imgUrl={item} key={index} />
})}
</div>
)
}
//style.js代碼如下 ${} 里面可以寫一個(gè)函數(shù),函數(shù)的形參就是一個(gè)對(duì)象 通過(guò)對(duì)象.屬性名可以訪問(wèn)到值
export const ListsItem = styled.div`
width: 280px;
height: 50px;
background: url(${(props) => props.imgUrl});
background-size: cover;
`
- 使用PureComponent優(yōu)化性能
- 父組件中的state和props中的數(shù)據(jù)發(fā)生變化的時(shí)候,render會(huì)重新執(zhí)行,并且子組件的render也會(huì)被執(zhí)行,但有時(shí)候子組件不需要重新渲染,此時(shí)可以用shouldComponentUpdate加入判斷邏輯決定子組件是否要重新渲染,PureComponent實(shí)現(xiàn)了shouldComponentUpdate,但最好結(jié)合immutable一起使用,不然可能會(huì)出錯(cuò)逆瑞。
項(xiàng)目地址:https://gitee.com/yyagami/jianshu.git
- 父組件中的state和props中的數(shù)據(jù)發(fā)生變化的時(shí)候,render會(huì)重新執(zhí)行,并且子組件的render也會(huì)被執(zhí)行,但有時(shí)候子組件不需要重新渲染,此時(shí)可以用shouldComponentUpdate加入判斷邏輯決定子組件是否要重新渲染,PureComponent實(shí)現(xiàn)了shouldComponentUpdate,但最好結(jié)合immutable一起使用,不然可能會(huì)出錯(cuò)逆瑞。