1. 项目搭建
- 使用create-react-app搭建项目
npm install -g create-react-app
create-react-app jianshu
- 关于css
一个.css
文件一旦被引入后,是在全局生效的。只要css类一致,样式都一致,就会造成两个css文件的相互冲突。所以引入第三方模块styled-components
,对样式进行统一管理,样式写在JS中。
- 安装
yarn add styled-components
- 引入
和引入CSS文件的方式一样
import "./style.js"
- 样式重置
不同浏览器对标签的默认样式是不同的,为了让样式在所有浏览器展示一样,全局使用reset.css
文件,让样式进行统一。
2. 动画效果实现
在react中,不要直接操作DOM,二叔操作数据,数据改变,页面自动更新渲染。
- 安装
react-transition-group
npm install react-transition-group // yarn add react-transition-group
- 使用
import {CSSTransition} from "react-transition-group"
- 属性
-
in
:控制组件应用动画的属性值,通常将一个react的组件state赋值给它,通过改变state,从而开启和关闭动画。 -
classNames[注意带s]属性
:用于当组件被应用动画时,不同的动画状态(enter,exits,done)
将作为className属性
的后缀来拼接为新的className
,如:
className="fade"
会被应用为fade-enter,fade-enter-active,fade-enter-done,fade-exit,fade-exite-active,fade-exit-done, fade-appear以及fade-appear-active.
每一个独立的className
都对应着单独的状态。 -
appear
:第一次是否有动画效果展示 - 其他钩子函数,
onEnter,onEntering,onEntered,onExit,onExiting,onExited
。
- 代码实现
<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}
- 针对多个DON元素的动画切换
import {CSSTransition,TransitionGroup} from "react-transition-group"
循环外部使用<TransitionGroup>
,内部具体元素使用<CSSTransition>
,<CSSTransition>
中只能有一个元素。
3. 使用react-redux进行数据管理
- 安装
yarn add redux //通过store对数据进行管理
yarn add react-redux // 方便在react中使用redux
- 使用
- store文件夹下---index.js,创建store,一个项目只能有一个store
import {createStore} from "redux"
import reducer from reducer.js
const store = createStore(store)
export default store
- 在入口文件App.js中使用
react-redux
import {Provider} from "react-redux"
import store from "./store/index.js"
//Provider这个提供器连接了store
//Provider下的所有子组件都可以对store中的数据进行获取及修改
<Provider store = {store}>
<Header/>
</Provider>
- 在Header.js中使用
connect连接store
import {connect} from "react-redux"
export default commect(mapState,mapDispatch)(Header)
mapState,mapDispatch
是一个函数返回一个对象,前者是获取到store
中的数据,后者是派发action
,对store
中的数据进行修改。组件通过this.props
使用相应的数据.
- 获取数据 ,组件的数据和
store
中的数据关系定义在mapstate
中。
const mapState = (store) => ({
//没有react-redux的时候,需要使用store.getState()进行数据获取,
//现在使用connect连接了store,可以直接获取到数据
data: store.data //使用时this.props.data
})
- 使用dispatch派发action,props如何对store中的数据做修改,定义在mapDispatch中。
const mapDispatch = (dispatch) => {
return {
handleChangeData(e){
const action = {
type: "change_data"
data: e.target.value
}
dispatch(action)
}
}
}
//组件中使用this.props.handleChangeData()
- store接收action,根据action.type在reduce.js中进行数据的修改
//定义初始化数据
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
}
- 注意点:
store
是唯一的,只有store
自己能改变自己的内容,reducer
可以接受state
,但是不能直接修改state
,所以必须进行深拷贝。reducer
必须是纯函数。使用react-redux
,之后,数据变化会直接显示到页面变化,而不需要调用store.subScribe()
方法监听数据变化了。connect
将UI组件和业务逻辑想结合,返回的就是容器组件。
4. 使用combineReducers完成对数据的拆分管理
- 浏览器扩展程序
redux-devtools-extension
现在所有的数据都是放在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对象(不可改变的对象)。
- 安装
npm install immutable // yarn install immutable
- 使用
- 在小reducer中使用immutable进行数据管理
//在分reducer.js文件中
import {fromJS} from "immutable"
const defaultState = fromJS({
data: false
})
- 此时,
state.header
就是一个immutable对象
,直接使用state.header.data
是不能获取到数据的,要使用immutable对象中调用数据的方法get()
//此时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
- 在小reducer中,
state
已经转变成immutable
对象,就不能直接对state进行修改,而是采用immutable的set()
方法进行修改。
export default (state=defaultState,action) => {
switch(action.type){
case "change_data" :
//一定要保证数据类型的统一
return state.set("data",fromJS(action.data))
default:
return state
}
}
immutable
的set()
方法结合之前的immutable对象
的值,和设置的值,返回一个全新的对象,并没有修改之前的immutable数据
。
- 小的reducer已经结合immutable 进行数据管理,在
大的reducer
中,也要作出相应的修改。
安装redux-immutable
yarn add redux-immutable
使用,使生成的reducer 是immutable对象。
import {combineReducers} from "redux-immutable"
- 总结: 通过reducx-immutable生成的reducer创建的state就是immuatable对象,就必须使用immutable的
get()
方法获取数据。采用redux生成的reducer创建的state就是JS对象,使用.
获取数据。
7. ajax获取数据
获取ajax
是异步操作,借助于redux-thunk
。他是redux 的中间件,指的是action和store
之间,在redux当中,action
只能是一个对象,使用dispatch
派发给store
,但使用了redux-thunk
之后,action可以是一个函数
。
- 安装
yarn add redux-thunk
- 对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;
- 发送ajax借助于第三方工具,axios。
- 安装
yarn add axios
- 对store中的数据,进行修改,要派发action
const mapDsiaptch = (dispatch) => {
handleChangeData(){
dispatch(actionCreators.getList())
}
}
- action为
getList()
,使用了redux-thunk
,之后是一个函数,派发ajax请求
。
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),
});
- store 接受action ,转给reducer进行数据修改
if(action.type === constants.CHANGE_LIST){
//采用immutable对象的set方法对数据进行修改,创建了新的数据List,并没有修改原数据list。
return state.set("list",action.data)
}
- ajax请求优化
一定要避免无意义的请求发送,提升性能。
if(list.size===0){
dispatch(actionCreators.getList())
}
以上代码等价于
(list.size===0) && dispatch(actionCreators.getList())
8. 采用DOM方式实现动画旋转效果
- CSS代码部分
.spin {
display: block;
float: left;
transition: all 1s ease-in;
ttransform-origin: center center; //以中心为旋转点
}
- ref获取到对应的DOM结构
<i ref = {(icon) => {this.spinIcon = icon}}
- 通过更概念rotate的值实现动画效果
//原生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. 路由
- 路由:就是根据URL的不同,显示不同的内容,点击URL变化,内容跟着变化,可以使用浏览器的前进后退,来展示不同的内容,这就叫路由。
- 安装
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的三个方法
-
merge()
方法可以代替链式set()
方法,一一次可以修改多个数据,接受一个对象
state.merge({
data: action.data.
list: action.list
})
-
getIn()
方法可以代替链式get()
方法,接受一个数组
state.getIn(["header","list"])
-
fromJS()
会把外层的数组和数组内部的元素转换成immutable对象
state.set("data",fromJS(action.data))
11.实现加载更多
- 实现加载更多就是发送ajax请求,获取数据。
- 获取数据,操作store,就是派发action,需要传递一个参数page。
- 具体代码实现
// 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.回到顶部
-
scrollTo()
滚动到文档中的某个坐标。 - 代码实现
<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.组件性能优化
- 组件重渲染优化
组件的数组都放在同一个store中,那么一个数据变化,所有的组件都会自动调用render()
函数,对组件进行重新渲染,很浪费性能。调用组件的shouldComponentUpdate()
,可以进行判断,当自身组件依赖的数据改变时,这个组件才进行重渲染,但是在每个组件中写一次这个方法,很麻烦,react
提供了PureComponent组件
,这个组件底层实现了shouldComponentUpdate()
,可以保证自身依赖数据变化,组件菜进行重渲染.
- 使用
import {PureComponent} from "react"
一般,PureComponent组件
和immutable
数据管理结合使用。
- 路由跳转优化
在react中实现路由跳转的时候,要使用React-Router-Dom
,这个第三方模块,这个模块的跳转是单页面跳转。不管怎么跳转,页面只会加载一次HTTP文件(HTML文件)。使用a标签实现的跳转,每跳转一次就会加载一次HTTP文件,所以不能使用a标签,React-Router-Dom
提供了Link
实现页面的跳转.
- 使用
import {Link} from "react-router-dom"
<Link to = "/"> //跳转到Header组件,减少了HTTP请求
14.页面路由参数传递
- 动态路由获取参数
点击跳转的时候,把id也传给它
<Link to = {'/detail/' + item.get("id")}/>
- 在路由配置的时候,访问detail路径,还需要传递一个额外的id参数
<Route path = "/detail/:id" exact component = {Detail}/>
- 在Detail组件中取到id的值,发送ajax请求接口时,把id传给后端
- 取到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.登录功能实现
- 功能:未登录,点击跳转登录页面,登陆成功,跳转到首页,并且可以跳转写文章页面,还可以点击退出登录。
- 登录部分代码实现
// 登录按钮部分代码
{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());
},
};
};
- 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);
- action部分代码实现
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,
});
- reducer.js实现
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"></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>
网友评论