React
React 在英文的意思是响应的意思
声明式开发
组件化
单向数据流
视图层框架
函数式编程
Fragment占位符,可以替代最外面的包裹层,这个解析编译的时候不作任何处理props接收传递的参数state改变,setState是异步函数bind改变this指向- 遍历, 删除
immutable(不可变的), 不允许直接通过this.state修改state的值,必须用this.setState- 引用样式,使用
className dangerouslySetInnterHtml将内容里面的代码,进行正常输出label的for,需要替换成htmlFor- 自定义组件引入和传值
- 单向数据流,不允许子组件对父组件直接修改值
- PropTypes和DefaultProps
- ref, 不建议使用ref, 数据驱动
// TodoItem
import React, { Component } from 'react'
import PropTypes from 'prop-types' // 脚手架自带
class TodoItem extends Component {
constructor(props) {
super(props)
this.handelClick = this.handelClick.bind(this)
}
render() {
const { item } = this.props
return (
<div onClick={handelClick} >{ item }</div>
)
}
handelClick() {
const {deleteItem, index } = this.props
deleteItem(index)
}
}
TodoItem.propsTypes = {
item: PropTypes.onOfType([PropTypes.string.isRequired, PropTypes.number]),
deleteItem: PropTypes.func,
index: PropTypes.number
}
TodoItem.defaultProps = {
item: 'defaultValue'
}
export default TodoItem
// TodoList
import React, { Component, Fragment } from 'react'
import TodoItem from './todoItem'
import './style.css'
class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
inputValue: '',
list: []
}
this.handleInputChange = this.handleInputChange.bind(this)
this.handleBtnClick = this.handleBtnClick.bind(this)
this.handleItemDel = this.handleItemDel.bind(this)
}
render() {
return (
<Fragment>
<label htmlFor="insertInput"></label>
<input
id="insertInput"
className="inputClass"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input) => {this.input = input}}
/>
<button onClick={handleBtnClick}>提交</button>
<ul>
{this.getTodoItem()}
</ul>
</Fragment>
)
}
getTodoItem() {
return this.state.list.map((item, index) => {
return (
<TodoItem
key={ index }
item={ item }
index={ index }
deleteItem = {this.handleItemDel}
/>
)
})
}
handleInputChange(e) {
const value = e.target.value
console.log(this.input.value, '和e.target.value是一致的')
this.setState(() => ({
inputValue: value
}))
}
handleBtnClick() {
this.setState((prevState) => ({
inputValue: '',
list: [...prevState.list, prevState.inputValue]
}
))
}
handleItemDel(e) {
this.setState((prevState) => {
let list = [...prevState.list]
list.splice(e, 1)
return {
list
}
})
}
}
export default TodoList
props, state 与 render的关系
当组件的state或者props改变的时候,就会触发render函数
当父组件的render被执行,子组件的render也会重新执行
虚拟DOM
虚拟dom,其实就是将数据保存成一个js对象,用这个js对象,去生成真实的dom 得益于虚拟dom,让react-native得以实现,因为移动端能够识别js对象,因此将js对象生成移动端的组件和代码。
- 当state,props发生改变时,将会生成新的虚拟dom
- 然后和之前的虚拟dom做对比
- 最后直接操作真实dom, 将有差异的进行修改替换
// JSX => createElement => 虚拟Dom(JS对象) => 真实的Dom
render (
return <div>item</div>
return React.createElment('div', {}, 'item')
return <div><span>item</span></div>
return React.createElment('div', {}, React.createElement('span', {}, 'item'))
)
Diff算法
- 就是两个虚拟dom的比对
- setState方法为何为异步执行,如果setState执行次数间隔很短,次数多,那么将多次setState合并成一个setState,将最开始和最新的进行比对,提升性能。
- 同级比对,只要检测同级不一致,就不会往下比对了,那么这层下面的dom直接会被重新渲染替换。
- key值尽量不要用index, 用item, 保证key值稳定,将新旧的虚拟dom快速建立关联,加快虚拟dom的比对性能
生命周期
在某一时刻,组件会自动执行的函数
-
Initialization
- Steup props and state
-
Mounting
componentWillMountRendercomponentDidMount
-
Updation
-
props
componentWillReceivePropsshouldComponentUpdatecomponentWillUpdateRendercomponentDidUpdate
-
states
shouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate
-
-
Unmounting
componentWillUnmount// 组件被移除的那一瞬间
注意事项
componentWillReceiveProps
当组件从父组件接收了参数,且出现在父组件的次数超过一次就会执行shouldComponentUpdate
判断子组件是否需要重新render,减少不必要的性能损耗
shouldComponentUpdate(nextState, nextProps) {
if(nextState.xxx != this.state.xxx) {
return true
} else {
return false
}
}
componentDidMount- 这个方法只执行一次,页面数据调用,应该放在这里。
- 不能放在
render中,会造成死循环 - 放在
constructor也行,但是最好放在componentDidMount中
动画 React-Transition-Group
yarn add react-transition-group
import React, { Component, Fragment } from 'react'
import { CSSTransition } from 'react-transition-group'
class App extends Component {
constructor(props) {
super(props)
this.state = {
show: true
}
this.handleToggle = this.handleToggle.bind(this)
}
render() {
return (
<Fragment>
<CSSTransition
in={this.state.show}
timeout={1000}
className="fade"
>
<div>hello</div>
</CSSTransition>
<button onClick={ this.handleToggle }>TOGGLE</button>
</Fragment>
)
}
handleToggle() {
this.setState((prevState) => {
return {
show: !prevState.show
}
})
}
}
export default App
容器组件和UI组件
UI组件只负责显示,不写逻辑 容器组件专门写逻辑方法,尽量减少UI代码
普通组件和无状态组件
无状态组件,就是一个函数,性能高 普通组件,会包含生命周期函数
redux
yarn add redux
// store.js 中转站
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store
// actionTypes.js
export const CHANGE_INPUT_VALUE = 'change_input_value'
export const ADD_ITEM = 'add_item'
// actionCreator.js
import { CHANGE_INPUT_VALUE, ADD_ITEM } from './actionTypes'
export const getInputValueAction = (value) => ({
type: CHANGE_INPUT_VALUE,
value
})
export const addItemAction = () => ({
type: ADD_ITEM
})
// reducer.js 存放数据
// 接收state, 不会修改state, state只能由store自己改变
// 不能有任何异步的操作,只能是纯函数,固定的输入,固定的输出,不会有任何副作用
import { CHANGE_INPUT_VALUE, ADD_ITEM } from './actionTypes'
const defaultState = {
inputValue: '',
list: [
'item1',
'item2',
'item3',
'item4'
]
}
export default (state = defaultState, action) => {
const newState = JSON.parse(JSON.stringify(state));
if (action.type == CHANGE_INPUT_VALUE) {
newState.inputValue = action.value;
}
if (action.type == ADD_ITEM) {
newState.list.push(newState.inputValue)
newState.inputValue = ''
}
return newState
}
// TodoList Component
import React, { Component, Fragment } from 'react'
import 'antd/dist/antd.css'
import { Input, Button, List } from 'antd'
import store from './store'
import { getInputValueAction, addItemAction } from './actionCreator'
class TodoList extends Component {
constructor(props) {
super(props)
this.state = store.getState()
this.handleInputChange = this.handleInputChange.bind(this)
this.handleStoreChange = this.handleStoreChange.bind(this)
this.handleButtonClick = this.handleButtonClick.bind(this)
store.subscribe(this.handleStoreChange)
}
render() {
return (
<Fragment>
<Input
placeholder="todi info"
value={this.state.inputValue}
onChange={this.handleInputChange}
></Input>
<Button type="primary" onClick={this.handleButtonClick}>提交</Button>
<List
bordered
dataSource={this.state.list}
renderItem={item => <List.Item>{ item }</List.Item>}
></List>
</Fragment>
)
}
handleInputChange(e) {
const action = getInputValueAction(e.target.value)
store.dispatch(action)
}
handleButtonClick() {
const action = addItemAction()
store.dispatch(action)
}
handleStoreChange() {
this.setState(store.getState())
}
}
export default TodoList
redux-thunk
可以使原本只能接收action为对象的store,支持接收一个action函数,这个action函数,可以使用异步调用数据,将数据再返回给store
yarn add redux-thunk
// store.js
import { createStore, applyMiddleware, compose } from 'redux';
import reduer from './reducer'
import thunk from 'redux-thunk'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose
const enhancer = composeEnhancers(applyMiddleware(thunk))
const store = createStore(
reducer,
enhancer
)
// actionCreator.js
export const initDataAction = (value) => ({
type: INIT_DATA,
value
})
export const getInitData() {
return (dispatch) => {
axios.get('list.json').then((data) => {
dispatch(initDataAction(data))
})
}
}
// TodoList component
import { getInitData } from './actionCreator'
componentDidMount() {
const action = getInitData()
store.dispatch(action)
}
redux-saga
yarn add redux-saga
// store.js
import { createStore, applyMiddleware, compose } from 'redux';
import reduer from './reducer'
import createSagaMiddleware from 'redux-saga'
import saga from './saga'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose
const sagaMiddleware = createSagaMiddleware()
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))
const store = createStore(
reducer,
enhancer
)
sagaMiddleware.run(saga)
// saga.js
// 文件从saga的官方网copy
import { takeEvery, put } from 'redux-saga/effects'
import { GET_INIT_DATA } from './actionTypes'
import { getInitDataAction } from './actionCreator'
function* getInitData() {
try {
const res = yield axios.get('./list.json')
const action = getInitDataAction(res.data)
yield put(action)
} catch(e) {
console.log(e)
}
}
function* mySaga() {
yield takeEvery(GET_INIT_DATA, getInitData)
}
export default mySaga
import { GET_INIT_DATA } from './actionTypes'
// actionCreator.js
export const getInitDataAction = (value) => ({
type: GET_INIT_DATA,
value
})
// TodoList component
import { getInitData } from './actionCreator'
componentDidMount() {
const action = getInitData()
store.dispatch(action)
}
styled-components
yarn add styled-components
// style.js
import styled, { injectGlobal } from 'styled-components'
injectGlobal`
body {
margin: 0;
padding: 0;
}
`
const headerWrapper = style.div`
height: 56px;
background: red;
`
// component
// 如果使用styled-components, 想获取dom节点本身,那么就不能用ref,此时ref拿到的是styled-components对象。需要用innerRef。
import React, { Component } from 'react'
import { headerWrapper } from './style'
class Header extends component {
render() {
return (
<headerWrapper innerRef={(header) => {this.header = header}} className="headerWrapper"></headerWrapper>
)
}
}
combineReducers
对数据进行拆分管理
// store/reducer.js
import { combineReducers } from 'redux'
import HeaderReducer from './components/header/store/reducer'
export default combineReducers({
header: HeaderReducer
})
// components/header/store/reducer.js
const defaultState = {
focused: false
}
export default (state = defaultState, action) => {
if(action.type == IS_FOCUSED) {
const newState = JSON.parse(JSON.stringify(state));
newState.focused = true
return newState
}
return state
}
// components/header
const mapStateToProps = (state) => {
return {
focused: state.header.focused
}
}
### immutable
生成一个不能随意改变的对象
`set()`方法, `get()`方法
``` shell
yarn add immutable
// components/header/store/reducer.js
import { fromJS } from 'immutable'
const defaultState = fromJS({
focused: false
})
export default (state = defaultState, action) => {
if(action.type == IS_FOCUSED) {
return state.set('focused', true)
}
return state
}
// components/header
const mapStateToProps = (state) => {
return {
focused: state.header.get('focused')
}
}
redux-immutable
统一数据管理
yarn add redux-immutable
// store/reducer.js
import { combineReducers } from 'redux-immutable'
import HeaderReducer from './components/header/store/reducer'
export default combineReducers({
header: HeaderReducer
})
export default reducer
// components/header
const mapStateToProps = (state) => {
return {
focused: state.get('header').get('focused'),
focused: state.getIn(['header','focused'])
}
}
react-router-dom
yarn add react-router-dom
// pages/home
import React, { Component } from 'react'
class Home extends Component {
render() {
return (
<div>Home</div>
)
}
}
export default Home
// app.js
import { BrowserRouter, Route } from 'react-router-dom'
import Home from './pages/home'
class App extends Component {
render () {
return (
<Provider store={store}>
<div>
<BrowserRouter>
<div>
<HeaderBar></HeaderBar>
<Route path="/" exact component={Home}></Route>
<Route path="/detail:id" exact component={Home}></Route>
<Route path="/comment" exact component={Home}></Route>
</div>
</BrowserRouter>
</div>
</Provider>
)
}
}
// navbar
import { Link } from 'react-router-dom'
<link to={'/detail/5'}></Link>
// detail
// 动态路由:获取地址栏参数 this.props.match.params.id
// comment
// ?形式:获取地址栏参数 this.props.location.search
PureComponent
- PureComponent通过prop和state的浅比较来实现shouldComponentUpdate,某些情况下可以用PureComponent提升性能
- 需要和immutable.js结合使用,不然会有很多坑
// PureComponent 源码
// shallowEqual从字面意思讲是"浅比较",那么什么事浅比较呢?
// 就是比较一个旧的props和新props的或者旧的state和新的state的长度是否一致,key是是否相同,以及它们对应的引用是否发生改变。仅仅做了一层的比较。所以这才叫做浅比较。
// 因此我们可以看到,当props或者state发生改变时,并不是直接去渲染,而是做了一层比较,才决定是否重新渲染该组件。
// 因此使用immutable会解决这个问题,immutable每次修改都会重新返回一个新的数据。
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps)
|| !shallowEqual(inst.state, nextStat e);
}
return shouldUpdate;
react-loadable
组件按需加载,需要配合react-router-dom中的withRouter使用,组件才能正常获取路由信息
yarn add react-loadable
// /home/loadable
import Loadable from 'react-loadable'
const LoadableComponent = Loadable({
loader: () => import('./home'),
loading: () => ({
return <div>正在加载</div>
})
})
export default () => LoadabledComponent
// /home
export default connect(null, null)(withRouter(Home))
import { BrowserRouter, Route } from 'react-router-dom'
import Home from './pages/home/loadable'
class App extends Component {
render () {
return (
<Provider store={store}>
<div>
<BrowserRouter>
<div>
<HeaderBar></HeaderBar>
<Route path="/" exact component={Home}></Route>
<Route path="/detail:id" exact component={Home}></Route>
<Route path="/comment" exact component={Home}></Route>
</div>
</BrowserRouter>
</div>
</Provider>
)
}
}
react-router 和 react-router-dom的区别
后者是在前者的基础上添加了功能,比前者多了个BroserRouter和Link
自己制作遇到的问题
- 包含
BrowserRouter的组件,不能使用withRouter - 包含
Provider的组件,不能使用connect - 页面第一次加载根目录,无法获取
props,需要重定向后才能获取到props,<Route render={(<Redirect to="/home" />)} />