笔记-React(记不清是啥版本了)

101 阅读7分钟

React

React 在英文的意思是响应的意思 声明式开发
组件化 单向数据流 视图层框架 函数式编程

  1. Fragment 占位符,可以替代最外面的包裹层,这个解析编译的时候不作任何处理
  2. props 接收传递的参数
  3. state 改变, setState是异步函数
  4. bind 改变this指向
  5. 遍历, 删除
  6. immutable(不可变的), 不允许直接通过this.state修改state的值,必须用this.setState
  7. 引用样式,使用className
  8. dangerouslySetInnterHtml 将内容里面的代码,进行正常输出
  9. labelfor,需要替换成 htmlFor
  10. 自定义组件引入和传值
  11. 单向数据流,不允许子组件对父组件直接修改值
  12. PropTypes和DefaultProps
  13. 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对象生成移动端的组件和代码。

  1. 当state,props发生改变时,将会生成新的虚拟dom
  2. 然后和之前的虚拟dom做对比
  3. 最后直接操作真实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算法

  1. 就是两个虚拟dom的比对
  2. setState方法为何为异步执行,如果setState执行次数间隔很短,次数多,那么将多次setState合并成一个setState,将最开始和最新的进行比对,提升性能。
  3. 同级比对,只要检测同级不一致,就不会往下比对了,那么这层下面的dom直接会被重新渲染替换。
  4. key值尽量不要用index, 用item, 保证key值稳定,将新旧的虚拟dom快速建立关联,加快虚拟dom的比对性能

生命周期

在某一时刻,组件会自动执行的函数

  • Initialization

    1. Steup props and state
  • Mounting

    1. componentWillMount
    2. Render
    3. componentDidMount
  • Updation

    • props

      1. componentWillReceiveProps
      2. shouldComponentUpdate
      3. componentWillUpdate
      4. Render
      5. componentDidUpdate
    • states

      1. shouldComponentUpdate
      2. componentWillUpdate
      3. render
      4. componentDidUpdate
  • Unmounting

    1. componentWillUnmount // 组件被移除的那一瞬间

注意事项

  1. componentWillReceiveProps
    当组件从父组件接收了参数,且出现在父组件的次数超过一次就会执行
  2. shouldComponentUpdate
    判断子组件是否需要重新render,减少不必要的性能损耗
shouldComponentUpdate(nextState, nextProps) {
    if(nextState.xxx != this.state.xxx) {
        return true
    } else {
        return false
    }
}
  1. 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

  1. PureComponent通过prop和state的浅比较来实现shouldComponentUpdate,某些情况下可以用PureComponent提升性能
  2. 需要和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-routerreact-router-dom的区别

后者是在前者的基础上添加了功能,比前者多了个BroserRouterLink

自己制作遇到的问题
  1. 包含BrowserRouter的组件,不能使用withRouter
  2. 包含Provider的组件,不能使用connect
  3. 页面第一次加载根目录,无法获取props,需要重定向后才能获取到props<Route render={(<Redirect to="/home" />)} />