REACT

193 阅读17分钟

基础

依赖

  • react.js
  • react-dom.js
  • babel.js

创建虚拟DOM

const VDOM = <h1>hello</h1>   //DOM是一个object对象

渲染虚拟DOM到页面

ReactDOM.render(VDOM, document.getElementById('root'))

jsx

  • 更加简单的创建虚拟DOM
  • 样式名: className
  • 内联样式:style={{color: 'red'}}
  • 虚拟DOM只有一个根标签
  • 标签必须闭合

函数式组件

  • 组件名必须首字母大写
  • 组件内必须return
  • 组件内的this为undefined,因为babel编译时开启了严格模式
function Demo(){
    return <h1>demo</h1>
}
// 渲染时Demo标签必须闭合
ReactDOM.render(<Demo />, document.getElementById('root'))

类式组件

  • 必须继承React.Component
  • 必须有render
  • 必须return
  • 类中可以直接写赋值语句:a=1,给所有类的实例对象添加一个属性a,值为1
执行类式组件
  • 找到Demo组件
  • 发现是类定义的组件,new出来该类的实例,并通过该实例调用原型上的render方法
  • 将render返回的虚拟DOM转为真实DOM,渲染到页面上
class Demo extends React.Component{
    render(){
        return {
            <h1>demo</h1>
        }
    }
}
// 渲染时Demo标签必须闭合
ReactDOM.render(<Demo />, document.getElementById('root'))

组件实例的三大核心属性

state

  • 不可直接更改,必须通过内置APIsetState进行更改
  • setState是对state进行合并,而不是替换

props

<Person name="tom" age="18"/>
  • props是只读的
  • 限制props: PropTypes 作用:验证父组件通过属性传递过来的数据的数据类型
  1. 导入 prop-types
    import PropTypes from 'prop-types'
    
  2. 如何验证:要在组件的 props 上进行类型检查,你只需配置特定的 propTypes 属性:
    // 子组件
    class One extends Component {
        // 属性验证写法二:
        static propTypes = {
            num:PropTypes.number
        }
    }
    // 属性验证写法一:
    // One.propTypes = {
    //     num:PropTypes.number
    // }
     
    // 父组件
    export default class Two extends Component {
        render(){
            return <One num={1}></One>
        }
    }
    
    • 单个条件

      static propTypes = {
          num:PropTypes.number
      }
      
    • 多个条件

      static propTypes = {
          num:PropTypes.oneOfType([
              PropTypes.number, 
              PropTypes.string,
              PropTypes.bool,
              PropTypes.array,
              PropTypes.object,
              PropTypes.func
          ])
      }
      
    • 限制数组里面的元素由某一种元素组成

      static propTypes = {
          num:PropTypes.arrayOf(PropTypes.number)
      }
      
    • 限制数组里面的元素由某几种类型的元素组成

      static propTypes = {
         num:PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string]))
      }
      
    • 指定一个对象某一类型的值组成

      static propTypes = {
          num:PropTypes.objectOf(PropTypes.number)
      }
      
    • 指定对象中的某属性的值由特定的类型组成

      static propTypes = {
          num:PropTypes.shape({
              a:PropTypes.number,
              b:PropTypes.string
          })
      }
      
    • 指定对象某属性的值由某类型组成

      static propTypes = {
          num:PropTypes.shape({
              a:PropTypes.oneOfType([ PropTypes.number,PropTypes.string, PropTypes.func]),
              b:PropTypes.string
          })
      }
      
    • 对象中某属性值不能为空

      static propTypes = {
          num:PropTypes.shape({
              id:PropTypes.number.isRequired
          })
      }
      
    • 枚举,指定是某个值

      static propTypes = {
          num:PropTypes.oneOf([1, 2]).isRequired
      }
      

refs

字符串形式的ref--不推荐使用了
  • <input ref="input1">
  • 使用
    • {input1} = this.refs 得到的是真实DOM
    • input1.value 得到的是输入框的值
回调refs
  • <input ref = {c=> this.input1 = c}
    • 其中c为当前节点(currentNode)
    • 给当前实例(this)新增一个属性inpu1,值为c
    • 此时input上的ref函数是一个回调函数,不需要我们自己调用,react会自动调用
    • 回调refs已内联函数定义,更新过程中,会被执行两侧,第一次传参为null,第二次才为节点
  • 使用
    • {value} = this.input1
  • 解决调用两次的问题
    • <input ref = {this.saveInput} />,saveInput是class的绑定函数
    • class里
    saveInput = c => {
        this.input1 = c
    }
    
creatRef 专人专用,只能存一个
  • class里
    • myRef = React.creatRef()
    • 返回容器(一个可以存储被ref所标识的节点)
  • <input ref={this.myRef} />
注:也可以通过event.target.value得到输入框的值
  • <input onBlur={this.show} />
show = event => {
    console.log(event.target.value)
}

受控组件与非受控组件

非受控组件

  • 现用现取
  • <input ref={c => this.username = c} />
  • 用时:this.username.value

受控组件

  • 存在state
  • <input onChange={this.saveUsername}/>
saveUsername = e => {
    this.setState({username: e.target.value})
}
  • 使用: const username = {this.state}
  • 受控组件不用使用ref

函数的柯里化(这里是为了参数)

  • 通过函数调用继续返回函数
  • <input onChange={this.saveFormData('username')}/>
    • saveFormData后面跟了小括号和参数,所以这个函数一开始就会调用
saveFormData = dataType => {
    return (event) => {
        //这个返回的箭头函数才是onChange时调用的函数
        this.setState = [dataType]: event.target.value
    }
}
  • 不用柯里化
    • <input onChange={event => this.saveFormData('username', event)}/>
    saveFormData = (dataType, event) => {
        this.setState = [dataType]: event.target.value
    }
    

生命周期 V16.8

  • componentWillMount(): 组件将要挂载
  • componentDidMount(): 组件挂载完毕
    • 初始化:开启定时器、发送网络请求、订阅消息
  • shouldComponentUpdate(): 控制组件更新的阀门,默认为true
  • componentWillUpdate(): 组件将要更新
  • componentDidUpdate(): 组件更新完毕
  • componentWillReceiveProps(): 组件将要接收props(第一次不算)
  • componentWillUnmount(): 组件将要卸载
    • 收尾:关闭定时器、取消订阅消息

渲染

  1. 初始化阶段:由ReactDOM.render()触发
  • constructor()
  • componentWillMount()
  • render()
  • componentDidMount()
  1. 更新阶段:由组件内的this.setState()或父组件的render触发
  • shouldComponentUpdate()
  • componentWillUpdate()
  • render()
  • componentDidUpdate()
  1. 卸载阶段:由ReactDOM.unmountComponentAtNode(document.getElementById('test'))触发
  • componentWillUnmount()

强制更新 forceUpdate

  • 不更改任何状态的数据,也可以更新页面
  • 阀门关了也能更新

生命周期 V17.0

  1. 废弃 componentWillMount、componentWillReceiveProps、componentWillUpdate
  2. 新增
    • static getDerivedStateFromProps:会在初始化和update时触发,用于替换componentWillReceiveProps,可以用来控制 props 更新 state 的过程;它返回一个对象表示新的 state;如果不需要更新,返回 null 即可
    • getSnapshotBeforeUpdate: 用于替换 componentWillUpdate,该函数会在update后 DOM 更新前被调用,用于读取最新的 DOM 数据,返回值将作为 componentDidUpdate 的第三个参数
class A extends React.Component {
  // 用于初始化 state
  constructor() {}
  
  // 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
  // 因为该函数是静态函数,所以取不到 `this`
  // 如果需要对比 `prevProps` 需要单独在 `state` 中维护
  static getDerivedStateFromProps(nextProps, prevState) {}
  
  // 判断是否需要更新组件,多用于组件性能优化
  shouldComponentUpdate(nextProps, nextState) {}
  
  // 组件挂载后调用
  // 可以在该函数中进行请求或者订阅
  componentDidMount() {}
  
  // 用于获得最新的 DOM 数据
  getSnapshotBeforeUpdate() {}
  
  // 组件即将销毁
  // 可以在此处移除订阅,定时器等等
  componentWillUnmount() {}
  
  // 组件销毁后调用
  componentDidUnMount() {}
  
  // 组件更新后调用
  componentDidUpdate() {}
  
  // 渲染组件函数
  render() {}
  
  // 以下函数不建议使用
  UNSAFE_componentWillMount() {}
  UNSAFE_componentWillUpdate(nextProps, nextState) {}
  UNSAFE_componentWillReceiveProps(nextProps) {}
}

样式模块化

  • import hello from './indedx.moduke.css'
  • <h2 className={hello.title}></h2>

子组件向父组件传递数据

  • 父组件给子组件传递一个函数,子组件调用
  • 父组件: <Header addTodo={this.addTodo} />
    • 父组件向子组件批量传递数据:<Item {...todo}/>: 展开todo,批量传递
addTodo = todoObj => {
    console.log(todoObj)
}
  • 子组件: this.props.addTodo({id: 1, title: 'xxx'})
  • nanoid插件: 获取唯一的id
    • npm i nanoid
    • import {nanoid} from 'nanoid'
    • nanoid()

消息订阅发布 PubSubJs

  • npm i pubsub-js
  • import PubSub from 'pubsub-js'
  • PubSub.publish('test', {name: 'zs', age: 18})
  • PubSub.subscribe('test', (_, data) => {...})

fetch

  • 浏览器内置
  • promise风格
const url = 'xxxx'
try{
    const res = await fetch(url)
    const data = res.json()
    console.log(data)
}catch(err){
    console.log(err)
}

React-Router

  • 使用的插件是react-router-dom
  • import { Link, Route, BrowserRouter, NavLink, Switch, Redirect } from 'react-router-dom'
  • 导航区:<Link className="" to="/about">About</Link>
  • 导航区:<NavLink activeClassName="active" to="/about">About</NavLink>: 可以自定义选择的样式,默认为active
  • 展示区:<Route path="/about" component={About}/>
    • About 是路由组件,一般放在pages文件夹下
    • 一般组件放在component文件夹下
  • Link/NavLink: 路由连接,引起路由变化
    • replace属性:使用该属性,跳转路由将不会在history中push一个浏览记录,而是取代上一个浏览记录。replace={true}, 或直接写replace
  • Route:注册路由
  • Redirect: 重定向,一般作为默认路由,放在Route的最下方
  • Switch: 用Switch组件把Route组件包裹,不会再继续往下匹配,可提高效率(一个以上路由)
  • exact: Route的属性,精准匹配(需要才开,不然会影响二级路由的匹配)
  • BrowserRouter/HashRouter: 路由器
  • Link和Route必须由BrowserRouter/HashRouter包裹,并且所有的只能包裹在一个路由器里面,所以一般都是直接在index.js中去包裹<App />
<Link replace to="/about">about</Link> | 
<Link to="/home"> home</Link>
<hr />
<div style={{background: 'red'}}>
  <Switch>
    <Route exact path="/" component={About}/>
    <Route path="/about" component={About}/>
    <Route path="/home" component={Home}/>
    <Redirect to="/home"/>
  </Switch>
</div>
  • 标签体包含在props.children里
    • <MyLink a="a">About</MyLink>
    • 在MyLink组件中,<NavLink {...this.props} /> => 其中props展开后包括children='About'
  • 解决多级路由样式丢失的问题
    • <link ref="stylesheet" href="/css/bt.css"/>, 不要样式路径中的.
    • <link ref="stylesheet" href="%PUBLIC_URL%/css/bt.css"/>
    • 用HashRouter替换BrowserRouter,#后面的不算在路由里(一般不用这种方式)

路由组件传参

params参数

<!-- 携带 -->
<Link to={`/about/detail/${msg.id}/${msg.title}`}>点击</Link>

<!-- 接收 -->
<Route path='/about/detail/:id/:title' component={Detail} />

<!-- 组件中获取 -->
this.props.match.params.id
this.props.match.params.title

search参数

<!-- 携带 -->
<Link to={`/about/detail/?id=${msg.id}&title=${msg.title}`}>点击</Link>

<!-- 无需接收 -->
<Route path='/about/detail' component={Detail} />

<!-- 组件中获取 -->
this.props.location.search  => ?id=1&title=title1

处理得到的字符串
import qs from 'querystring'
const search = this.props.location.search
const {id, title} = qs.parse(search.slice(1))
console.log(id, title) // 1, title1

qs.stringify({name: 'sz', age: 18})  //name=sz&age=18
  • querystring 是react自带的库
    • querystring.parse:将一个字符串反序列化为一个对象
    • querystring.stringify:将一个对象序列化为字符串

state参数(清缓存会丢数据)

<!-- 携带 -->
<Link to={{pathname: '/about/detail', state: {id: msg.id, title: msg.title}}}>点击</Link>

<!-- 无需接收 -->
<Route path='/about/detail' component={Detail} />

<!-- 组件中获取 -->
const {id, title} = this.props.location.state || {}
console.log(id, title)  //1, title1
  • BrowserRouter
    • H5的history API,不兼容IE9以下版本
    • 对路由state:没有影响,保存在history中
  • HashRouter
    • 使用URL的哈希值
    • 对路由state:state数据会丢失

编程式导航

  • <link> 和<NavLink> 实现路由的跳转 是声明式导航
  • 通过js路由对象的方式叫做编程式导航 push replace go
  • 路由对象只有被router处理过之后或者用WithRouter高阶组件处理过后才有
  • 即只有路由组件才有this.props.history/location/match
//params
this.props.hsitory.replace(`/home/detail/${id}/${title}`)
this.props.hsitory.push(`/home/detail/${id}/${title}`)

//search
this.props.history.replace(`/home/detail?id=${id}&title={$title}`)
this.props.history.push(`/home/detail?id=${id}&title={$title}`)

//state
this.props.history.replace('/home/detail', {id, title})
this.props.history.push('/home/detail', {id, title})

//其他方法
this.props.history.go(-1)
this.props.history.goBack()  //返回
this.props.history.goForward()  //前进

WithRouter

  • import {WithRouter} from 'react-router-dom'
  • export default WithRouter(Header)
  • WithRouter的返回值是一个新组件

redux

  • 状态管理的js库
  • 管理react中多个组件共享的状态

bg2016091802.jpg

首先,用户发出 Action。

store.dispatch(action);

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

let nextState = todoApp(previousState, action);

State 一旦有变化,Store 就会调用监听函数。

// 设置监听函数
store.subscribe(listener);

listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

function listerner() {
  let newState = store.getState();
  component.setState(newState);   
}

store

  • store就是保存数据的地方,整个应用只能有一个store
  • Redux 提供createStore这个函数,用来生成 Store
  • createStore函数接受另一个函数作为参数,返回新生成的 Store 对象
  • store提供了3个方法
    • store.getState()
    • store.dispatch()
    • store.subscribe()

state

  • Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。当前时刻的 State,可以通过store.getState()拿到。

action

  • 同步action,action的值是一个对象, {type: 'ACTIONNAME', data: data}
  • 异步action,action的值是函数,因为函数可以开启一个异步任务
  • reduce第一次调用,是store自动触发的

store.dispatch()

  • store.dispatch()是 View 发出 Action 的唯一方法。
  • store.getState()
  • store.dispatch()
  • store.subscribe()

reducer

  • Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
  • Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
  • store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
  • Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出

store.subscribe()

  • Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数
// redux/store.js
import { createStore, applyMiddleware } from 'redux'
import countReducer from './count_reduce'
import thunk from 'redux-thunk'

// export default createStore(countReducer)   只有同步action
export default createStore(countReducer, applyMiddleware(thunk))  //有异步action时可以用thunk中间件
// redux/count_reduce.js
import { INCREMENT, DECREMENT } from './constants'
const initState = 99
function countReducer(preState = initState, action){
  const { type, data } = action
  switch(type){
    case INCREMENT:
      preState = preState + data
      break
    case DECREMENT:
      preState = preState - data
      break
    default:
      return preState
  }
  return preState
}
export default countReducer
// redux/constants.js 常量文件
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
// redux/count_action_creator.js   这里面定义的是store.dispatch调用的action
import { INCREMENT, DECREMENT } from './constants'
export const increment = data => ({type: INCREMENT, data})
export const decrement = data => ({type: DECREMENT, data})
export const incrementAsync = (data, time) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(increment(data))
    }, time)
  }
}
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

ReactDOM.render(<App />, document.getElementById('root'))
import React, { Component } from 'react'
import store from './redux/store'
import {increment, decrement, incrementAsync} from './redux/count_action_creator'

export default class App extends Component {
  state = {
    count: 0
  }
  componentDidMount() {
    store.subscribe(() => {
      this.setState({})
    })
  }
  increment = () =>{
    const { value } = this.selectedNumber
    store.dispatch(increment(value*1))
  }
  decrement = () =>{
    const { value } = this.selectedNumber
    store.dispatch(decrement(value*1))
  }
  incrementIfOdd = () =>{
    const { value } = this.selectedNumber
    const  count  = store.getState()
    if(count % 2 !== 0){
      store.dispatch(increment(value*1))
    }
  }
  incrementAsync = () =>{
    const { value } = this.selectedNumber
    // setTimeout(() => {
    //   store.dispatch(increment(value*1))
    // }, 500)
    store.dispatch(incrementAsync(value*1, 500))
  }
  render() {
    return (
      <div>
        <h1>总数为:{ store.getState() }</h1>
        <select ref={c => this.selectedNumber = c}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>&nbsp;
        <button onClick={ this.increment }></button>&nbsp;
        <button onClick={ this.decrement }></button>&nbsp;
        <button onClick={ this.incrementIfOdd }>奇数加</button>&nbsp;
        <button onClick={ this.incrementAsync }>异步加</button>&nbsp;
      </div>
    )
  }
}

react-redux

  • React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
  • UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑
  • 如果一个组件既有 UI 又有业务逻辑,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图
  • React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成

UI组件

  • 一般子组件是UI组件
  • 不能使用任何redux的api
  • 只负责页面的呈现,交互,不带有任何业务逻辑
  • 没有状态,即没有this.state
  • 所有数据都由参数(this.props)提供
  • 因为不含有状态,UI 组件又称为"纯组件",即它纯函数一样,纯粹由参数决定它的值

容器组件

  • 一般父组件是容器组件
  • 放在containers文件夹下
  • 与redux通信,使用redux的api,将结果给UI组件
  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态

connect

  • React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);  //TodoList是UI组件,VisibleTodoList是connect生成的容器组件
  • connect(mapStateToProps,mapDispatchToProps) 接收2个参数
    • mapStateToProps
      • 是一个函数,它接受state作为参数,返回一个对象,传递给UI组件
      • 执行后应该返回一个对象,里面的每一个键值对就是一个映射
      • mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。
      • mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。
      • connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
    • mapDispatchToProps
      • 它可以是一个函数,也可以是一个对象
      • 如果mapDispatchToProps是一个函数,会得到dispatchownProps(容器组件的props对象)两个参数
      • 如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。(简易形式)
      • 返回操作状态的方法,传递给组件

<Provider> 组件

  • connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
  • 一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。
  • React-Redux 提供Provider组件,可以让容器组件拿到state
  • 使用React-Router的项目,与其他项目没有不同之处,也是使用ProviderRouter外面包一层,毕竟Provider的唯一功能就是传入store对象。
const Root = ({ store }) => (
  <Provider store={store}>
    <Router>
      <Route path="/" component={App} />
    </Router>
  </Provider>
)

计数器实例

// redux 文件夹下的constants.js, count_action_creator.js, count_reduce.js, store.js 同上

//index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './redux/store'

//会自动给容器组件传递store
ReactDOM.render(
  <Provider store={ store }>    
    <App />
  </Provider>, document.getElementById('root'))

//用了react-redux不用再检测redux的变化
// store.subscribe(() => {
//   ReactDOM.render(<App />, document.getElementById('root'))
// })      
    
// App.jsx
import React, { Component } from 'react'
import Count from './containers/Count'
// import store from './redux/store'
export default class App extends Component {
  render() {
    return (
      <div>
        { /* <Count store={store} />  */}
        <Count/>     //优化后这里不用再传store
      </div>
    )
  }
}
// containers/Count.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { increment, decrement, incrementAsync } from '../../redux/count_action_creator'

// 将UI组件直接合并在容器组件中,不用再暴露出去
class Count extends Component {
  increment = () =>{
    const { value } = this.selectedNumber
    this.props.add(value*1)
  }
  decrement = () =>{
    const { value } = this.selectedNumber
    this.props.dec(value*1)
  }
  incrementIfOdd = () =>{
    const { value } = this.selectedNumber
    const { count } = this.props
    if(count % 2 !== 0){
      this.props.add(value*1)
    }
  }
  incrementAsync = () =>{
    const { value } = this.selectedNumber
    this.props.addAsync(value*1)
  }
  render() {
    console.log(this.props)
    return (
      <div>
        <h1>总数为:{ this.props.count }</h1>
        <select ref={c => this.selectedNumber = c}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>&nbsp;
        <button onClick={ this.increment }></button>&nbsp;
        <button onClick={ this.decrement }></button>&nbsp;
        <button onClick={ this.incrementIfOdd }>奇数加</button>&nbsp;
        <button onClick={ this.incrementAsync }>异步加</button>&nbsp;
      </div>
    )
  }
}

//这里connect第二个参数是一个对象,dispatch会自动分发action
export default connect(
  state => ({count: state}), 
  {
    add: increment,
    dec: decrement,
    addAsync: incrementAsync
  } 
)(Count)

合并reducer

src 目录结构

  • containers
    • Count
      • index.jsx
    • Person
      • index.jsx
  • redux
    • actions
      • count.js
      • person.js
    • reducers
      • count.js
      • person.js
    • constants.js
    • store.js
  • App.jsx
  • index.js
// redux/store.js
import { createStore, applyMiddleware, combineReducers } from 'redux'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
import thunk from 'redux-thunk'

const allReducers = combineReducers({
  count: countReducer,
  person: personReducer
})

export default createStore(allReducers, applyMiddleware(thunk))
// redux/contants.js
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
// redux/reducers/count.js
import { INCREMENT, DECREMENT } from '../constants'
const initState = 99
function countReducer(preState = initState, action){
  const { type, data } = action
  switch(type){
    case INCREMENT:
      return preState = preState + data
    case DECREMENT:
      return preState = preState - data
    default:
      return preState
  }
}
export default countReducer


// redux/reducers/person.js
import { ADD_PERSON } from '../constants'
const persons = [
  {id: '1', name: 'zs', age: 18},
  {id: '2', name: 'ls', age: 19},
]
function personReducer(preState = persons, action) {
  const { type, data } = action
  switch(type) {
    case ADD_PERSON:
      return [data, ...preState]
    default: 
      return preState
  }
}

export default personReducer
// redux/actions/count.js
import { INCREMENT, DECREMENT } from '../constants'
export const increment = data => ({type: INCREMENT, data})
export const decrement = data => ({type: DECREMENT, data})
export const incrementAsync = (data, time) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(increment(data))
    }, 500)
  }
}


// redux/actions/person.js
import { ADD_PERSON } from '../constants'
export const addPerson = data => ({type: ADD_PERSON, data})
// containers/Count/index.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { increment, decrement, incrementAsync } from '../../redux/actions/count'

class Count extends Component {
  increment = () =>{
    const { value } = this.selectedNumber
    this.props.add(value*1)
  }
  decrement = () =>{
    const { value } = this.selectedNumber
    this.props.dec(value*1)
  }
  incrementIfOdd = () =>{
    const { value } = this.selectedNumber
    const { count } = this.props
    if(count % 2 !== 0){
      this.props.add(value*1)
    }
  }
  incrementAsync = () =>{
    const { value } = this.selectedNumber
    this.props.addAsync(value*1)
  }
  render() {
    return (
      <div>
        <h1>这是Count组件,Person组件总共有{ this.props.personCount }人</h1>
        <h3>总数为:{ this.props.count }</h3>
        <select ref={c => this.selectedNumber = c}>
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
        </select>&nbsp;
        <button onClick={ this.increment }></button>&nbsp;
        <button onClick={ this.decrement }></button>&nbsp;
        <button onClick={ this.incrementIfOdd }>奇数加</button>&nbsp;
        <button onClick={ this.incrementAsync }>异步加</button>&nbsp;
      </div>
    )
  }
}

export default connect(
  state => ({count: state.count, personCount: state.person.length}), 
  {
    add: increment,
    dec: decrement,
    addAsync: incrementAsync
  }
)(Count)
// containers/Person/index.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { nanoid } from 'nanoid'
import { addPerson } from '../../redux/actions/person'


class Person extends Component {
  addPerson = () => {
    const name = this.nameNode.value
    const age = this.ageNode.value*1
    const person = {
      id: nanoid(),
      name,
      age
    }
    this.props.addPerson(person)
    this.nameNode.value = ''
    this.ageNode.value = ''
  }
  render() {
    return (
      <div>
        <h1>这是Person组件, Count组件的结果为{this.props.count}</h1>
        <div>
          <input type="text" ref={c => this.nameNode = c} placeholder='请输入名字'/>
          <input type="text" ref={c => this.ageNode = c} placeholder='请输入年龄'/>
          <button onClick={ this.addPerson }>添加</button>
        </div>
        {
          this.props.persons.map(item => {
            return <li key={item.id}>name: {item.name} -- age: {item.age}</li>          
          })
        }
      </div>
    )
  }
}


export default connect(
  state => ({persons: state.person, count: state.count}),
  {
    addPerson
  }
)(Person)
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import store from './redux/store'

ReactDOM.render(
  <Provider store={store}>
      <App />
  </Provider>, document.getElementById('root'))

// store.subscribe(() => {
//   ReactDOM.render(<App />, document.getElementById('root'))
// })
// App.jsx
import React, { Component } from 'react'
import Count from './containers/Count'
import Person from './containers/Person'

export default class App extends Component {
  render() {
    return (
      <div>
        <Count/>
        <hr/>
        <Person />
      </div>
    )
  }
}

redux的reduce必须是纯函数

  • 同样的输入,得到同样的输出
  • 不得改写参数数据
  • 不会产生任何副作用,如网络请求,输入和输出设备
  • 不能调用Date.now()或Math.random()等不纯的方法

其他

setState

  • 同步方法,但引起的后续动作是异步的
// 1. 对象式
setState({}, () => {
    // 第二个参数是一个回调函数,可选。在render后调用
})

// 2.函数式: 依赖原状态
setState((state, props) => {
    return {count: state.count+1}
}, () => {})

懒加载 lazy

  • lazy是一个函数,需要传入一个函数
import {lazy, Suspense} from 'react'
const Home = lazy(() => import('./Home'))
// 可以抽成Loading组件,但是不能用懒加载的方式来加载
<Suspense fallback={<h1>Loading...</h1>}>
    <Route path="xxx" component={Home} />
    ...
</Suspense>

函数式组件的勾子 Hooks

  1. react.useState(initValue) 模拟state
  • useState 中的函数只会在初始化的时候执行一次。
  • 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。
import {useState} from 'react'
export default function App(props){
  const [count, setCount] = useState(0)
 
  return (
    <div>
      <h1>当前次数:{count}</h1>
      <button onClick={() => setCount(count+1)}>点击+1</button>
      <button onClick={() => setCount(count => count + 1)}>点击+1</button>
    </div>
  )
}

当设置为异步更新,点击按钮延迟到3s之后去调用setCount函数,当快速点击按钮时,也就是说在3s多次去触发更新,但是只有一次生效,因为 count 的值是没有变化的。而当使用函数式更新 state 的时候,这种问题就没有了,因为它可以获取之前的 state 值,也就是代码中的 prevCount 每次都是最新的值。

function Counter() {
  const [count, setCount] = useState(0);
  function handleClick() {
    setTimeout(() => {
      setCount(count + 1)
    }, 3000);
  }
  function handleClickFn() {
    setTimeout(() => {
      setCount((prevCount) => {
        return prevCount + 1
      })
    }, 3000);
  }
  return (
    <>
      Count: {count}
      <button onClick={handleClick}>+</button>
      <button onClick={handleClickFn}>+</button>
    </>
  );
}
  1. useEffect(): 模拟生命周期勾子
import React, { useEffect } from 'react';

function Welcome(props) {
  useEffect(() => {
    document.title = '加载完成';
  });
  return <h1>Hello, {props.name}</h1>;
}
  • 组件每渲染一次,useEffect()函数就自动执行一次。组件首次在网页 DOM 加载后,useEffect()函数也会执行。
  • useEffect() 的第二个参数: 不希望useEffect()每次渲染都执行,这时可以使用它的第二个参数,使用一个数组指定副效应函数的依赖项,只有依赖项发生变化,才会重新渲染。
function Welcome(props) {
  useEffect(() => {
    document.title = `Hello, ${props.name}`;
  }, [props.name]);
  return <h1>Hello, {props.name}</h1>;
}

上面例子中,useEffect()的第二个参数是一个数组,指定了第一个参数(副效应函数)的依赖项(props.name)。只有该变量发生变化时,副效应函数才会执行。

如果没有第二个参数,则监测所有的state。如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行

  • useEffect()的用途
    • 获取数据(data fetching)
    • 事件监听或订阅(setting up a subscription)
    • 改变 DOM(changing the DOM)
    • 输出日志(logging)
  • useEffect()的返回值
    • useEffect()允许返回一个函数,在组件卸载时,执行该函数,清理副效应。如果不需要清理副效应,useEffect()就不用返回任何值。

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    subscription.unsubscribe();
  };
}, [props.source]);
  1. useRef: 模拟ref
import {useRef} from 'react'
const myRef = useRef()
<input ref={myRef}/>
获取值: myRef.current.value

import {useEffect, useRef} from 'react'
export default function App(props){
  const myRef = useRef()
  useEffect(() => {
    document.title = 'test'
  })
  const changeInput = () => {
    console.log(myRef)
    const inputValue = myRef.current.value
  }
  return (
    <div>
      <h1>Test...{props.a}</h1>
      <input type="text" ref={myRef} onChange={changeInput} />
    </div>
  )
}

Fragment

  • 替代div,少一层结构
  • 只允许写key属性值(遍历时)
  • 也可以直接用空标签<></>,但是空标签不允许写任何属性

Context

  • 用于父与孙之间的通信,越级通信