React实战一

时间:2022-08-08 11:41:16

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.jsHeader.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

https://reactjs.org/docs/typechecking-with-proptypes.html

当组件之间传值的时候,接收一方往往需要对参数进行校验,这就是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 after defaultProps are resolved

class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
} render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}

1.5 生命周期函数

在页面加载的过程中,特定时间点自动执行的函数称为生命周期函数。

生命周期的四个阶段:

  1. Initialization

这一阶段主要加载props和state

  1. Mounting:页面第一次加载的时候才会执行的挂载

componentWillMount:在组件即将被挂载到页面的时刻执行

render:

componentDisMount:在组件挂载完成之后会被执行

​ 使用场景:用于发送ajax请求

  1. 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:

  1. 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中需要注意的点:

  1. 标签的类class 如:<div class='show'></div> 会和ES6关键字冲突会使用className代替
  2. <label></label> for属性也会使用htmlFor 代替

3. 虚拟DOM

虚拟DOM就是一个JS对象,用来描述真实DOM

jsx转换成js对象

<div className='show'>hello world</div>
React.createElement('div',{className: 'show'}, 'hello world');

流程讲解

  1. state数据
  2. jsx模板
  3. 数据 + 模板生成虚拟DOM
  4. 使用虚拟DOM来来生成真实DOM, 显示在页面上
  5. state发生改变
  6. 数据 + 模板生成新的虚拟DOM
  7. 比较原始的虚拟DOM和新的虚拟DOM之间的区别,找到不同之处
  8. 操作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

  1. this.state = store.getState() 从store中获取数据
  2. store.subscribe(this.handSubscribe)订阅数据,监听数据的变化
  3. 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, useapplyMiddleware()

这里配合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 Redux combineReducers 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 />
)
}