React实战一
1. 搭建环境
npm install -g creat-react-app
# 创建一个项目
create-react-app jianshu
cd jianshu
npm run start
# 安装styled-components 全局统一管理css
npm install --save styled-components
# 全局样式引入
https://meyerweb.com/eric/tools/css/reset/
background-size: contain;
2. React知识点
1. 组件
组件:就是将整个UI拆分为很多小的,可重用的UI,每一个小的UI独立做自己的事情。
1.1 定义一个组件
一个组件需要继承React.Component
重写render()
函数,返回JSX
注意点:
1.只要有JSX的地方必须引入
React
2.返回值中只能有一个元素包裹,
div
包裹整个JSX,如下是错误的返回方式return ( <div> hello world </div> <div> hello world </div> );
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div>
hello world
</div>
);
}
}
export default App;
1.2 组合与拆分组件
引用官方的一句话:
Don’t be afraid to split components into smaller components.
尽可能的让每一个组件都能分工明确,减少重复,加大可重用。例如表格组件,就必须被拆分为一个单独的组件,它可能在各个地方被用到。
组件拆分优点:让开发显得更加清晰明了。
未拆分组件看起来就没有食欲
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
1.案例解析
将首页拆分为Header, Right,如下定义一个首页组件,在首页组件中分别引入Header和Right组件,这就是一个简单的组件拆分和组合.
<React.Fragment />
是一个虚拟的组件,仅仅是为了包裹其中的元素,并不会出现在页面上
Index.js
import React from 'react'
import Header from './Header'
import Right from './Right'
class Index extends React.Component {
render() {
return (
<React.Fragment>
<Header />
<Right />
</React.Fragment>
);
}
}
export default Index
Header.js
import React from 'react'
class Header extends React.Component {
render() {
return (
<React.Fragment>
<div>i am header component</div>
</React.Fragment>
);
}
}
export default Header
Right.js
import React from 'react'
class Right extends React.Component {
render() {
return (
<React.Fragment>
<div>i am right component</div>
</React.Fragment>
);
}
}
export default Right
1.3 组件传值
组件传值分为以下几种情况:1. 父组件传值到子组件;2.子组件向父组件传值;3.隔代组件传值
1. 父传子
注意点: props是只读的
例如Index.js
向Header.js
组件传值
{/* Index.js 传递title属性 */}
return (
<React.Fragment>
<Header title="Header Title"/>
<Right />
</React.Fragment>
);
{/*Header.js 接受值*/}
return (
<React.Fragment>
<div>{this.props.title}</div>
</React.Fragment>
);
1.4 state
state可以用来存储数据,记性状态管理。
it is private and fully controlled by the component.
render函数执行:组件state和props发生改变,render函数会被执行,当父组件重新渲染时候,子组件render函数也会被重新执行
class Index extends React.Component {
constructor(props) {
super(props)
this.state = {
title: 'Header title'
}
}
render() {
return (
<React.Fragment>
<Header title={this.state.title}/>
<Right />
</React.Fragment>
);
}
}
export default Index
1.5 PropTypes
当组件之间传值的时候,接收一方往往需要对参数进行校验,这就是PropTypes的作用
import PropTypes from 'prop-types'
class ReactUI extends React.Component {
render() {
return (
)
}
}
//属性类型
ReactUI.propTypes = {
//表示name必须是string类型
name: PropTypes.string
//属性必传
id: PropTypes.element.isRequired
}
默认值
The
propTypes
typechecking happens afterdefaultProps
are resolved
class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
}
render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}
1.5 生命周期函数
在页面加载的过程中,特定时间点自动执行的函数称为生命周期函数。
生命周期的四个阶段:
- Initialization
这一阶段主要加载props和state
- Mounting:页面第一次加载的时候才会执行的挂载
componentWillMount
:在组件即将被挂载到页面的时刻执行
render
:
componentDisMount
:在组件挂载完成之后会被执行
使用场景:用于发送ajax请求
- Updation:数据发生变化的时候会被执行
- props:
componentWillReceiveProps
:
子组件从父组件接受属性,第一次存在父组件,render函数执行,该函数不会被执行,当父组件render函数重新被执行,就会执行这个函数
shouldComponentUpdate
:组件是否需要被更新,返回boolean值
使用场景:shouldComponentUpdate(nextProps, nextState);这两个参数来接受即将改变的props和state的值;演示:
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.title !== this.props.title) { //放生改变,则需要重新渲染
return true
} else {
return false
}
}
componentWillUpdate
: 当组件被更新之前被执行,但是取决于shouldComponentUpdate
的返回结果,
render
componentDidUpdate
:组件更新完成之后会被执行
- states:
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
:
- Unmounting
componentWillUnmount
: 组件从页面移除的时候会被执行
1.6 无状态组件
无状态组件: 就是组件中只含有render()函数的组件,
import React from 'react'
class Header extends React.Component {
render() {
return (
<React.Fragment>
<div>xx {this.props.title}</div>
</React.Fragment>
);
}
}
export default Header
这种组件可以被简化为无状态组件
export default (props, context) => {
return (
<React.Fragment>
<div>xx {props.title}</div>
</React.Fragment>
);
}
1.7 List and Key
案例:使用List集合渲染一个页面
key的作用:
Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity
这个主要和虚拟DOM有关,可以提高虚拟DOM性能。
兄弟姐妹之间的key必须独一无二
import React from 'react'
class Event extends React.Component {
constructor(props) {
super(props)
this.state = {
data: [1, 2, 3, 4, 5, 6, 7]
}
this.handClick = this.handClick.bind(this);
}
render() {
return (
<div>
<ul>
{
this.state.data.map((item) => (
<li key={item}>{item}</li>
))
}
</ul>
</div>
)
}
}
export default Event
2. JSX
JSX
既不是string也不是html,是React独有的一种语法,jsx中需要注意的点:
- 标签的类
class
如:<div class='show'></div>
会和ES6关键字冲突会使用className代替 <label></label>
for属性也会使用htmlFor
代替
3. 虚拟DOM
虚拟DOM就是一个JS对象,用来描述真实DOM
jsx转换成js对象
<div className='show'>hello world</div>
React.createElement('div',{className: 'show'}, 'hello world');
流程讲解
- state数据
- jsx模板
- 数据 + 模板生成虚拟DOM
- 使用虚拟DOM来来生成真实DOM, 显示在页面上
- state发生改变
- 数据 + 模板生成新的虚拟DOM
- 比较原始的虚拟DOM和新的虚拟DOM之间的区别,找到不同之处
- 操作DOM,改变不同的地方
diff算法
上面的第七步骤是如何比对不同之处呢,这里使用了diff算法。
同层比对:第一层有差异,则不会再进行比对,直接向下全部渲染,如果第一层相同,则向下继续比较。
根据Key做关联来进行比对: 同层的key必须固定不变,这样才能再进行比对的时候准确的找到原始的dom节点。假如使用index作为key值,当删除一个元素,那个被删除元素后面的所有标签key值都会被改变,那么进行比对的时候,就会出现性能消耗。
5. 函数绑定
React events are named using camelCase, rather than lowercase.
例如
onClick
,onBlur
...
import React from 'react'
import './event.css'
class Event extends React.Component {
constructor(props) {
super(props)
this.state = {
isShow: true
}
}
render() {
return (
<div>
<button onClick={this.handClick.bind(this)}>切换</button>
<div className={this.state.isShow ? 'show':'hidden'}>你好</div>
</div>
)
}
handClick() {
this.setState((state) => ({
isShow: !state.isShow
}))
}
}
export default Event
传递参数
render() {
return (
<div>
<button onClick={() => this.handClick(this.state.isShow)}>切换</button>
<div className={this.state.isShow ? 'show':'hidden'}>你好</div>
</div>
)
}
handClick(isShow) {
this.setState((state) => ({
isShow: !isShow
}))
}
传递参数的几种方式
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
<SearchSwitch onClick={() => switchItem(page, totalPage)}>换一批</SearchSwitch>
3. Redux
https://redux.js.org/introduction/getting-started
# 安装
npm install --save redux
1. 了解是三个概念
Actions
: 就像一个快递,里面包含地址(行为),以及物品(数据)
Reducers
:就像一个快递员,负责分发不同的快递,根据地址(行为)派送到不同地方
Store
: 就像一个总站,可以存储这些快递,最后返回给用户。
与物流之间的区别就是:store不可变,只可以返回数据,而原先的数据一直保留在store里面
1.1 演示
第一步:创建Store index.js
这里借助redux createStore方法创建store,并且引入reducer
import { createStore } from 'redux'
import reducer from './Reducers'
const store = createStore(
reducer,
// 这个可以使用Chrome redux 插件进行调试
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
export default store
第二步:创建一个reducer
reducer 里面保存数据的初始状态,
defaultState
解析action的行为
const defaultState = {
list: ['hello', 'world']
}
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'add_list_action':
return {list: action.data}
default:
return state
}
}
export default reducer
第三步:使用redux
- this.state = store.getState() 从store中获取数据
- store.subscribe(this.handSubscribe)订阅数据,监听数据的变化
handClick()
方法中主要定义action,然后利用store进行分发
import React from 'react'
import store from './store'
class ReduxUI extends React.Component {
constructor(props) {
super(props)
this.state = store.getState()
this.handClick = this.handClick.bind(this)
this.handSubscribe = this.handSubscribe.bind(this)
store.subscribe(this.handSubscribe)
}
render() {
return (
<div>
<button onClick={this.handClick}>点击我</button>
<ul>
{
this.state.list.map((item) => {
return <li key={item}>{item}</li>
})
}
</ul>
</div>
)
}
handClick() {
//创建一个Action
const addListAction = () => ({
type: 'add_list_action',
data: ['1', '2', '3']
})
//这是一个异步操作
store.dispatch(addListAction())
}
handSubscribe() {
this.setState(store.getState())
}
}
export default ReduxUI
2. react-redux中间件
改造1>1.1 中的代码
nmp install --save react-redux
新增加一个父组件,统一管理store,
这个组件拥有store,
<Provider store={store}>
表示改下面的所有元素都可以使用store
中的数据
import React, { Component } from 'react';
import store from './pages/redux/store'
import { Provider } from 'react-redux'
import ReduxUI from './pages/redux/ReduxUI'
class App extends Component {
render() {
return (
<Provider store={store}>
<ReduxUI/>
</Provider>
);
}
}
export default App;
步骤三中的修改
第一步:添加
connect
方法第三步:映射state中的属性,定义分发的方法
第四步:连接组件
import React from 'react'
import {connect } from 'react-redux'
class ReduxUI extends React.Component {
render() {
//从react-redux中接收store中的信息,以及分发action的方法
const { list, handClick } = this.props
return (
<div>
<div>
<button onClick={handClick}>点击我</button>
<ul>
{
list.map((item) => {
return <li key={item}>{item}</li>
})
}
</ul>
</div>
</div>
)
}
}
//state就是store中state
const mapStateToProps = (state) => ({
list: state.list
})
//这里定义分发action的方法,dispatch 就是store的dispatch方法
const mapDispatchProps = (dispatch) => ({
handClick() {
//创建一个Action
const addListAction = () => ({
type: 'add_list_action',
data: ['1', '2', '3']
})
dispatch(addListAction())
}
})
//连接组件,并且把属性和action行为传递给组件
export default connect(mapStateToProps, mapDispatchProps)(ReduxUI)
4. 其他
1. Redux-thunk
Redux Thunk middleware allows you to write action creators that return a function instead of an action
npm install redux-thunk
to enable Redux Thunk, use
applyMiddleware()
这里配合redux-devtools-extension调试工具一起使用
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
// 定义一个方法,处理鼠标聚焦行为
//然后dispatch分发action
handFocus(list) {
(list.size === 0) && dispatch(actionCreator.getSearchItems())
dispatch(actionCreator.inputFocusAction())
}
//另外一个文件
//===================================
// 这里就是上面的action,这个action是一个方法,在该方法里面发送ajax异步请求,
//异步请求之后继续分发另外一个action
export const getSearchItems = () => {
return (dispatch) => {
axios.get("/api/item.json").then((res) => {
dispatch(searchItemsAction(res.data.data))
})
}
}
正如官方文档所说:redux-thunk允许你分发一个方法类型的action
2. Redux-saga
asynchronous things like data fetching and impure things like accessing the browser cache
npm install --save redux-saga
import { createStore, compose, applyMiddleware } from 'redux'
import reducer from './Reducers'
import createSagaMiddleware from 'redux-saga'
import reduxSaga from './reduxSaga'
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
reducer,
composeEnhancers(applyMiddleware(sagaMiddleware))
)
//运行自己的reduxSaga
sagaMiddleware.run(reduxSaga)
export default store
reduxSaga.js
import { takeEvery, put } from 'redux-saga/effects'
import axios from 'axios'
function* getJson() {
const res = yield axios.get('./data.json')
yield put({type:"add_list_action", data: res.data}) //继续派发action
}
function* reduxSaga() {
// 当store分发'add_list_action' 这个action的时候,会调用getJson方法
yield takeEvery("add_list__pre_action", getJson)
}
export default reduxSaga
5. immutable
immutable可以保证数据一旦创建,就不会被改变
npm install --save immutable
//目的:将store变成不可变,这是一个简单reducer.js
import * as constants from './constant' //es6语法
import { fromJS } from 'immutable'
//这里将defaultStore变成一个immutable对象
const defaultStore = fromJS({
focused: false,
mouseIn: false,
list: [],
page: 1,
totalPage: 1
})
export default (state = defaultStore, action) => {
switch (action.type) {
case constants.input_focused:
//这里的set immutable对象的一个值,其实并不是改变了state,而是返回一个新的对象
//通过set可以简化我们的操作,不用重新构建全部的对象
return state.set('focused', true)
default:
return state
}
}
// 如何获取一个immutable对象呢?
const mapStateToProps = (state) => ({
//这里表示获取header组件下的一个immutable对象
//这里是 redux-immutable 获取数据的形式
// state.header.get('focused') 为immutable获取数据的格式
focused: state.getIn(['header', 'focused']), //state.get('header').get('focused')
mouseIn: state.getIn(['header', 'mouseIn']),
list: state.getIn(['header', 'list']),
page: state.getIn(['header', 'page']),
totalPage: state.getIn(['header', 'totalPage'])
})
//通过action传递参数的时候也应该是一个immutable对象
export const searchItemsAction = (data) => ({
type: constants.render_list,
data: fromJS(data),
totalPage: Math.ceil(data.length / 10)
})
5.1 redux-immutable
redux-immutable
is used to create an equivalent function of ReduxcombineReducers
that works with Immutable.jsstate.
我们经常面临很多很多组件,但是如果把所有组件的store,reducer, action 维护在一个目录下,那将是惨目忍睹的,所以通常情况下我们会分开管理redux,然后将所有的reducer组合在一起
npm install --save redux-immutable
//import { combineReducers } from 'redux'
import { combineReducers } from 'redux-immutable' //可以管理immutale对象
import { reducer as headerReducer} from '../header/store'
const reducer = combineReducers({
header: headerReducer
})
export default reducer
5. react-router-dom
React Router is a collection of navigational components that compose declaratively with your application.
npm install --save react-router-dom
import { BrowserRouter, Route } from "react-router-dom"
...
<BrowserRouter>
{/* 表示路由到一个组件 */}
<Route path="/home" exact component={Home} />
</BrowserRouter>
Route
: 表示要路由的到哪一个组件
Link
: 表示一个连接,跳转到某个页面
6. react-loadable
一个项目,不同的页面应该按需加载,而不是当首页出来之后加载所有的组件,例如我们访问首页的时候只会加载首页相关的资源,访问详情页的时候加载详情页的代码,这样可以提高首页访问速度以及用户体验
npm install --save react-loadable
案例演示
import React from 'react'
import Loadable from 'react-loadable'
const LoadableComponent = Loadable({
//这里`reactUI`是一个组件,表示这个组件将会被按需加载
loader: () => import('./reactUI'),
// 这里是一个函数,可以是一个组件,表示页面加载前的状态
loading: () => <div>正在加载</div>
})
//返回无状态组件
export default () => {
return <LoadableComponent/>
}
7. 在React 使用各种CSS
1. styled-components
npm install --save styled-components
定义全局样式
import { createGlobalStyle } from 'styled-components'
export const ResetCSS = createGlobalStyle`
body {
line-height: 1;
}
....
`
// 引用全局样式
import { ResetCSS } from './style'
...
render() {
return (
<ResetCSS />
)
}