React 从〇开始 入坑

448 阅读12分钟

这是一篇十分十分十分基础的笔记,不要报过高的期望......如有错误的地方,还请大佬指正

1 React基础

1.1 JSX语法

&nbsp&nbsp&nbsp&nbspJSX语法是一种类似于html标签的语法,它的作用相当于是让我们在JavaScript代码中直接写html代码,但是JSX不完全是html,它是JavaScrip 的一种扩展语法,它具有JavaScript的全部能力,我们还可在JSX代码中插入变量或者表达式,用JSX语法写出来的语句是一个对象,我们可以将它存为一个变量,这个变量作为ReactDOM对象的render方法的第一个参数。

<!-- 加载React -->
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 转换JSX 生产环境中不建议使用 浏览器中使用Babel来编译JSX效率非常低 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

&nbsp&nbsp&nbsp&nbspjsx可以是嵌套结构,如果是嵌套结构,需要有唯一的一个外层标签<></>。如果是单标签,在结尾要加"/",在jsx中可以通过"{}"插入变量,表达式或者函数调用。

<div id="root"></div>
<script type="text/babel">
  // script type="text/babel"
  const num = 0
  const str = 'Tifa'
  const isOk = true
  const revStr = str => str.split('').reverse().join('')
  const el = (
    <div>
      {/* jsx注释定义方法 */}
      <h3>jsx语法</h3>
      {/* 插入变量及运算 */}
      <p>{ num + 1 }</p>
      {/* 插入表达式 */}
      <p>{ str.split('').reverse().join('') }</p>
      {/* 插入函数调用 */}
      <p>{ revStr(str) }</p>
      {/* 插入三元运算表达式 */}
      <p>{ isOk?'YES':'NO' }</p>
      {/* 内嵌样式&行间样式 */}
      <p className="bg-red">内嵌样式</p>
      <p style={{width:'200px',height:'200px',backgroundColor:'cyan'}}>行内样式</p>
      {/* 单标签,结尾要加"/" */}
      <img src="../images/tifa.png" />
    </div>
  )
  ReactDOM.render(el,document.getElementById('root'))
</script>

1.2 函数组件

&nbsp&nbsp&nbsp&nbsp组件可以理解成是一个组成页面的部件或者零件,每个部件都有自己完整的结构和功能,多个部件拼装在一起就可以组成一个页面,组件最终是要返回一个jsx对象,组件有两种定义方式:一种是函数式定义,一种是类定义。

// 组件名称首字母要大写
function Welcome () {
  return <h1>Hello,Tifa</h1>
}
// 函数可接收props参数,props是一个对象,对象中的属性是在使用组件时传入的
function WelcomeName (props) {
  return <h1>Hello, {props.name}</h1>
}
// <WelcomeName name='Tifa' />

&nbsp&nbsp&nbsp&nbsp组件的渲染和 jsx 对象一样,我们可以通过 ReactDOM.render() 方法来渲染组件,组件以标签的方式使用,可以写成单个标签或者双标签,写成单个便签,结尾要加"/"

// ReactDOM.render(<Welcome />, document.getElementById('root')
ReactDOM.render(<WelcomeName name="Tifa" />, document.getElementById('root')
// 参数用解构
const WelcomeName = ({name}) => <h1>Hello, {name}</h1>
// 拼装组件
const App = () => (
  <div>
    <WelcomeName name="Tifa" />
    <WelcomeName name="Aerith" />
    {/* <WelcomeName name="Cloud" /> */}
  </div>
)
ReactDOM.render(<App />, document.getElementById('root'))

1.3 类组件

&nbsp&nbsp&nbsp&nbsp类组件就是通过ES6类Class的方式来定义组件。定义的类需要继承于React.Component类,类组件最少需要有一个render方法,这个方法会返回一个jsx对象,类里面使用的props属性是在React.Component类上面继承过来的,所以可以直接使用。

class WelcomeName extends React.Component {
  render () {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
ReactDOM.render(<WelcomeName name="Tifa" />,document.getElementById('root')

&nbsp&nbsp&nbsp&nbsp组件以标签的形式使用时,可以用单标签,也可以用双标签,如果以双标签的方式使用,在双标签中插入内容,那么组件的props上就有了children属性,children属性值就是组件标签中间的内容,可以是文本、jsx对象、函数或者组件。

class App extends Component {
  render () {
    return <Cloud>{ () => alert(Date.now()) }</Cloud>
  }
}
class Cloud extends Component {
  render () {
    return (
      <>
        <h2>children属性:</h2>
        <button onClick={ this.props.children }>点我显示时间戳</button>
      </>
    )
  }
}

1.4 事件绑定

&nbsp&nbsp&nbsp&nbspReact事件绑定与js的行间事件类似,事件绑定是写在标签中的,事件用小驼峰命名,事件需要传递一个函数作为事件处理程序,可以通过类定义组件,将这个函数作为一个方法定义在组件中。

class Hello extends React.Component {
// 建议使用箭头函数 不用操心this的问题
info = () => {
  alert(`名字:${this.props.name}--年龄:${this.props.age}`)
}

skill = name => {
  alert(`技能名:${name}`)
}

render () {
  return (
    <div>
      <input type='button' value='点我呀' onClick={this.info} />
      {/* 给绑定函数传递参数时 用箭头函数包裹一下 */}
      <input type='button' value='放大招' onClick={()=>this.skill('小拳拳捶你~~~')} />
    </div>
  )
}
}
ReactDOM.render(
<Hello name='Tifa' age={18} />,
document.getElementById('root')
)

1.5 State

&nbsp&nbsp&nbsp&nbsp拥有state属性的组件叫做有状态组件,没有state属性的组件叫做无状态组件。所以用函数方式定义的组件都是无状态组件,用类的方式定义的组件,定义了state就是有状态组件,没定义state就是无状态组件。

<div id="root"></div>
<script type="text/babel">
  class Increase extends React.Component {
    constructor (props) {
      super(props)
      this.state = {
        num : 0
      }
    }
    addNum = () => {
      this.setState(state => ({ num: state.num + 1 }))
    }
    // 设置state里面的值用setState,不能直接修改state中的值
    // setState里面可以直接传递一个对象,对象可以是整体或者是部分的state(键值对)
    // setState里面还可以传递一个函数返回一个对象
    // 函数的参数中传递的第一个参数是state上一个状态的值
    render () {
      return (
        <div>
          <p> { this.state.num } </p>  
          <button onClick={ this.addNum }>点我递增</button>
        </div>
      )
    }
  }
  ReactDOM.render(<Increase />, document.getElementById('root'))
</script>

&nbsp&nbsp&nbsp&nbsp注意: React 可能会把多个 setState() 调用合并成一个调用。props和state可能会异步更新,所以你不要依赖他们的值来更新下一个状态。使用函数的形式,这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数,我们可以在这个值基础上修改。

this.setState((state, props) => ({
  num: state.num + props.num
}))

1.6 列表渲染

const list = ['Tifa', 'Cloud', 'Aerith', 'Vincent']
const seventhHeaven = (
  <ul>
    {
      list.map((item, index) => (
        <li key={index}>
          {item} --- {index}
        </li>
      ))
    }
  </ul>
)
ReactDOM.render(seventhHeaven, document.getElementById('root'))

1.7 条件渲染

  • 根据条件返回不同的结构
function TifaOrAerith (props) {
  if (props.isTifa === true) {
    return <h2>Tifa~~~</h2>
  }else{
    return <h2>Aerith~~~</h2> 
  }
}
ReactDOM.render(<TifaOrAerith isTifa={true} />, document.getElementById('root'))
  • 根据条件返回不同的组件
function Tifa (props) {
  return <h2>Tifa~~~</h2>
}
function Aerith (props) {
  return <h2>Aerith~~~</h2>
}
function TifaOrAerith (props) {
  if (props.isTifa === true) {
    return <Tifa />
  }else{
    return <Aerith />
  }
}
ReactDOM.render(<TifaOrAerith isTifa={true} />, document.getElementById('root'))
  • 使用 && 运算符
function TifaIsMine (props) {
  return <h2>Tifa is mine~~~</h2>
}
class Tifa extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      isTifa: true,
      msg: 'Tifa is mine~~~'
    }
  }
  render () {
    const { isTifa, msg } = this.state
    return (
      <div>
        <h2>悄悄的告诉你</h2>
        {isTifa && <h2>{msg}</h2>}
        {/* {isTifa && <TifaIsMine />} */}
      </div>
    )
  }
}
ReactDOM.render(<Tifa />, document.getElementById('root'))

1.8 表单

&nbsp&nbsp&nbsp&nbsp表单元素对应着数据,我们想实现双向数据绑定的效果,需要在表单元素上绑定onchange事件,state中的值改变表单元素的值随之改变。这种表单的值和state中的值进行绑定的组件叫做受控组件。

class MyForm extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      username: '',
      password: ''
    }
  }
  // e是事件对象
  // e.target指的是发生事件的元素
  changeValue = e => {
    this.setState({
      [e.target.name]: e.target.value
    })
  }
  render () {
    const { username, password } = this.state
    return (
      <form>
        <div>
          <label>用户名:</label> {username}
          <br />
          <input
            type='text'
            name='username'
            value={username}
            onChange={this.changeValue}
          />
        </div>
        <div>
          <label>密码:</label> {password}
          <br />
          <input
            type='text'
            name='password'
            value={password}
            onChange={this.changeValue}
          />
        </div>
      </form>
    )
  }
}

1.9 Ref

&nbsp&nbsp&nbsp&nbsp获取子组件实例或者dom元素可以用ref来实现,Refs 是使用 React.createRef() 创建,在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。

class Myform extends React.Component {
  constructor (props) {
    super(props)
    this.state = {}
    // 在组件初始化的时候创建一个ref对象
    this.myref = React.createRef()
  }
  // 在组件挂载到页面之后自动执行的方法
  componentDidMount () {
    // 让输入框获得焦点
    this.myref.current.focus()
  }
  render () {
    return (
      // 在标签中通过ref关联创建的ref对象
      <input type='text' ref={this.myref} />
    )
  }
}
// 用箭头函数 ❤❤❤
class Myform extends React.Component {
  componentDidMount () {
    this.myRef.focus()
  }
  render () {
    return <input ref={ ele => this.myRef = ele } />
  }
}
// 这个就看看吧↓
class Myform extends React.Component {
  componentDidMount () {
    this.refs.myRef.focus()
  }
  render () {
    return <input ref='myRef' />
  }
}

2.0 生命周期

常用的生命周期方法

  • constructor: 在组件初始化的时候会自动执行
  • render: 这个方法会在组件开始执行,也会在state和props的数据发生变化时执行
  • componentDidMount: 组件加载完成,能够获取真是的 DOM 在此阶段可以发ajax请求、绑定事件
  • componentDidUpdate: 这个方法在组件更新之后执行
  • componentWillUnmount: 这个方法在组件卸载和销毁之前直接调用,此阶段可以清除定时器、事件绑定

&nbsp&nbsp&nbsp&nbsp不常用的生命周期方法 shouldComponentUpdate ,这个方法在组件更新之前执行,不过这个方法可以决定是否要更新组件,它需要返回一个布尔值,如果返回一个true,进入render方法,然后接着进入 componentDidUpdate 方法,如果返回一个false,那么执行会停留在当前方法上,视图就不会更新了。

2.1 用脚手架

脚手架工具为create-react-app,临时安装create-react-app工具并且生成项目包my-app

npx create-react-app my-app
cd my-app
npm start

2.2 数据传递

  • 父子组件传值
    • 父组件向子组件传值,可以使用子组件的props属性,将父组件的值传进去。
    • 子组件向父组件传值,可以使用子组件的props属性,将父组件的一个方法的引用传递到子组件中,子组件调用这个方法,将子组件中的数据传递出来给父组件
import React, { Component } from 'react'

class Father extends Component {
  constructor (props) {
    super(props)
    this.state = {
      name: 'Tifa',
      age: 18
    }
  }
  // 传递给子组件的函数
  giveSonFunc = num => {
    this.setState({
      age: num
    })
  }
  render () {
    const { name, age } = this.state
    return (
      <>
        <h2>这是父组件</h2>
        <p>子组件传递过来的年龄:{ age }</p>
        <hr />
        <Son
          name={ name }
          giveAppData={ this.giveSonFunc }
        />
      </>
    )
  }
}

class Son extends Component {
  render () {
    return (
      <>
        <h2>这是子组件</h2>
        <p>父组件传递过来的名字:{ this.props.name }</p>
        {/* 通过函数的参数向父组件传递值 */}
        <button onClick={ () => { this.props.giveAppData(20) } }>点我传值给父组件</button>
      </>
    )
  }
}
  • 兄弟组件传值

使用events模块,实例化EventEmitter类,通过emit方法来发送数据,通过on方法来接收数据。

import React, { Component } from 'react'
import { EventEmitter } from 'events'
// 实例化bus对象,用于组件间的传值
const bus = new EventEmitter()
class App extends Component {
  render () {
    return (
      <>        
        <Tifa />
        <hr />
        <Aerith />
      </>
    )
  }
}

class Tifa extends Component {
  constructor (props) {
    super(props)
    this.state = {
      name: ''
    }
  }
  componentDidMount () {
    // 接收值
    bus.on('xxx', data => {
      this.setState({ name: data.name })
    })
  }
  render () {
    return (
      <>
        <h2>蒂法组件</h2>
        <p>艾莉丝组件传递过来的名字:{ this.state.name } </p>
      </>
    )
  }
}

class Aerith extends Component {
  giveTifaName = () => {
    // 传递值
    bus.emit('xxx', { name: 'Aerith' })
  }
  render () {
    return (
      <>
        <h2>艾莉丝组件</h2>
        <button onClick={ this.giveTifaName }>点我传递名字给蒂法组件</button>
      </>
    )
  }
}

export default App

2.3 PropTypes类型检查

&nbsp&nbsp&nbsp&nbspPropTypes 提供很多验证器 (validator) 来验证组件接收到的数据类型是有效的。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。

// 安装校验规则包
npm i prop-types
import propTypes from 'prop-types'

class App extends Component {
  render () {
    return (
      <>        
        <h3>propTypes校验</h3>
        {/* 数值类型 */}
        <p>{ this.props.num }</p>
        {/* 数组类型 */}
        <p>{ this.props.arr }</p>
        {/* 函数类型 */}
        <p>{ this.props.func }</p>
        {/* 多个值中的一个 */}
        <p>{ this.props.enum }</p>
        {/* 多个类型中的一个 */}
        <p>{ this.props.union }</p>
      </>
    )
  }
}

App.propTypes = {
  num: propTypes.number.isRequired,
  arr: propTypes.array,
  func: propTypes.func,
  enum: propTypes.oneOf(['Tifa', 'Aerith']),
  union: propTypes.oneOfType([
    propTypes.string,
    propTypes.number
  ])
}

// 校验错误的在控制台 console 中反馈
// 常用的校验类型:array、bool、func、number、object、string
// 必填项:isRequired
// 多个值中一个:oneof
ReactDOM.render(<App
  num={ 111 }
  enum='Tifa'
  union='222'
/>, document.getElementById('root'))

&nbsp&nbsp&nbsp&nbsp组件的props中的属性值可以不传入,然后通过配置特定的 defaultProps 属性来定义 props 的默认值

class App extends Component {
  render () {
    return (
      <>        
        <p>{ this.props.name }</p>
      </>
    )
  }
}
App.defaultProps = {
    name: 'Tifa'
}

2.4 Render Props

&nbsp&nbsp&nbsp&nbsp如果多个组件的部分功能相同,我们可以将这些部分相同的功能封装成一个组件,再通过一定的模式,将这个组件的功能赋予其他组件,从而达到组件功能复用的目的,复用组件的功能,其实就是复用组件的state以及操作state的方法,我们可以用render-props模式,也可以使用高阶组件(HOC)。

class App extends Component {
  render () {
    return (
      <>        
        <h3>Render Props模式</h3>
        <Tifa xxx={ ({ name }) => <p>名字:{ name } </p> } />
      </>
    )
  }
}
class Tifa extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      name: 'Tifa'
    }
  }
  render () {
    return this.props.xxx(this.state)
  }
}

使用children属性来实现

class App extends Component {
  render () {
    return (
      <>        
        <h3>Render Props模式</h3>
        {/* <Tifa xxx={ state => <ShowName {...state} /> } /> */}
        {/* ↓↓↓使用children属性↓↓↓ */}
        <Tifa>{ state => <ShowName {...state} /> }</Tifa>
      </>
    )
  }
}
class Tifa extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      name: 'Tifa'
    }
  }
  render () {
    // return this.props.xxx(this.state)
    // ↓↓↓使用children属性↓↓↓
    return this.props.children(this.state)
  }
}
const ShowName = ({ name }) => <p>名字:{ name } </p>

2.5 高阶组件(HOC)

&nbsp&nbsp&nbsp&nbsp组件功能复用还可以使用高阶组件(HOC, Higher-Order Component),高阶组件是一个函数,它接收一个组件,返回 增加了功能 的组件。高阶组件内部创建了一个组件,组件内部提供可复用的状态和逻辑,传入的组件作为它的子组件,组件通过props属性将状态传递给这个子组件。最终这个函数将组件返回,返回的组件相当于是在传入的组件基础上增加了额外的功能。

// 定义一个高阶组件
function Hoc (Comp) {
  class Tifa extends Component {
    constructor (props) {
      super(props)
      this.state = {
        name: 'Tifa'
      }
    }
    render () {
      return <Comp { ...this.state } {...this.props} />
    }
  }
  return Tifa
}
const ShowNameAge = ({ name, age }) => <p>名字:{ name } -- 年龄:{ age } </p>
// 传递一个组件
const App = Hoc(ShowNameAge)

ReactDOM.render(<App age='18'/>, document.getElementById('root'))

2.6 React路由

通过react-router-dom模块来实现路由

// 安装包
npm i react-router-dom
// 导入对应的依赖
import { HashRouter, Route, Link, Switch, Redirect } from 'react-router-dom'
  • HashRoter: 哈希路由,路由组件的容器标签
  • Route: ★ 组件的容器标签,path属性和Link的to属性的地址对应,component对应相应的组件
  • Link: 路由的链接,通过to属性来定义链接地址,最终呈现为a标签
  • Switch: 如果需要定义404跳转和重定向跳转,需要用此标签包裹Route标签
  • Redirect: 路由重定向,通过from属性定义原始路由,通过to属性定义重定向路由
// 基本的使用
import { HashRouter, Link, Route, Switch, Redirect } from 'react-router-dom'

function Tifa () {
  return <h2>我是Tifa</h2>
}
function Aerith () {
  return <h2>我是Aerith</h2>
}
function Cloud () {
  return <h2>我是Cloud</h2>
}
function NotFound () {
  return <h2>404 Not Found</h2>
}
class App extends Component {
  render () {
    return (
      <HashRouter>
        <Link to="/">蒂法</Link><br/>
        <Link to="/aerith">艾丽丝</Link><br/>
        <Link to="/cloud">克劳德</Link>
        <hr />
        {/* Switch包裹路由, 匹配到就不往下匹配, NotFound要放在最下面 */}
        <Switch>
          {/* exact属性是让path精确匹配,否则"/"和"/tifa"的都会匹配 */}
          {/* <Route exact path="/" component={ Tifa } /> */}
          <Route path="/tifa" component={ Tifa } />
          <Route path="/aerith" component={ Aerith } />
          <Route path="/cloud" component={ Cloud } />
          {/* 重定向要写在404上面和路由的最下面 */}
          <Redirect exact from="/" to="/tifa" />
          {/* 404页面 */}
          <Route component={ NotFound } />
        </Switch>
      </HashRouter>
    )
  }
}

还可以自定义一个路由组件,我们使用Route组件的children属性,这样可以根据路由的匹配 动态调整界面

// 将上面的略作修改
// 先自定义一个路由组件
function CustomLink ({ label, exact, to }) {
  return (
    <Route
      path={to}
      exact={exact}
      // 不论path匹配与否都想渲染些东西时可以使用children属性
      // match由系统传入, 匹配当前路由则返回一个对象, 不匹配返回null
      children={
        sb => {
          return (
            <Link
              to={to}
              style={ sb.match?{color:'lightgreen'}:{color:'cyan'} }
            >
              { label }
            </Link>
          )
        }
      } 
    />
  )
}
// 替换上面的<Link />,这样再点击链接时会更改样式
<CustomLink label="蒂法" to="/tifa" />
<CustomLink label="艾丽丝" to="/aerith" />
<CustomLink label="克劳德" to="/cloud" exact={ true } />

路由跳转,编程式导航的实现

this.props.history.push('/tifa')

路由传参,1. 通过params的方式传参/:xxx,通过props属性来获取路由传递过来的参数props.match.params.xxx; 2. 通过编程式导航携带

// 动态路由的方式params
function Tifa (props) {
  return <h2>收到的名字是: {props.match.params.name}</h2>
}
<Route path="/ff7/:name" component={ Tifa } />

// query的方式↓
// props.history.push({ path: '/ff7', query: { name: 'Tifa'} })
// props.location.query.name 接收参数
// 编程时导航的方式,最好用state传递/接收参数
// 通过props.history.push({})来跳转并传递参数
function Tifa (props) {
  return (
    <>
      <h2>我是Tifa</h2>
      <button onClick={()=>props.history.push({
        pathname: '/aerith',
        state: {
          name: 'Tifa'
        }
      })}>去找Aerith</button>
    </>
  )
}
// 通过props.history.location.state来接收参数
function Aerith (props) {
  return (
    <>
      <h2>我是Aerith</h2>
      <p>谁要过来: { props.history.location.state && props.history.location.state.name }</p>
    </>
  )
}

非路由组件获取props.history用withRouter方法包一下,withRouter会将history,location,match对象传到props上面

经常会用到子路由,直接在子组件使用路由,不用使用<HashRouter>包裹

function Buster () {
  return <p>Buster</p>
}
function Sword () {
  return <p>Sword</p>
}
function Cloud () {
  return (
    <>
      <h2>我是Cloud</h2>
      <Link to="/cloud/buster">Buster</Link><br />
      <Link to="/cloud/sword">Sword</Link>
      <Route exact path="/cloud/buster" component={Buster} />
      <Route exact path="/cloud/sword" component={Sword} />
    </>
  )
}
class App extends Component {
  render () {
    return (
      <HashRouter>
        <Link to="/cloud">Cloud</Link>
        <hr />
        {/* 可以将component={()=><Cloud></Cloud>} */}
        <Route path="/cloud" component={ Cloud } />
      </HashRouter>
    )
  }
}
// component={()=>(<Cloud>Cloud里面的东西都可以放到这里</Cloud>)}
// 然后Cloud里面用props.children替换

2.7 Redux

&nbsp&nbsp&nbsp&nbsp简单地说就是公共状态管理的工具,类似于vuex。对于复杂的组件关系,组件间的数据传递就变得非常复杂难以维护,redux就是一种解决方法,将数据放在一个地方进行集中管理,所有的组件都共享这一个地方的数据。

Redux基本流程:(参考阮一峰大大的教程)

  1. 用户发出Action store.dispatch(action)
  2. Store自动调用Reducer,并且传入两个参数:当前State和收到的Action。Reducer会返回新的State。
  3. State一旦有变化,Store就会调用监听函数。store.subscribe(listener)
  4. listener可以通过store.getState()得到当前状态。
listerner: () => {
  this.setState(store.getState())
}

建一个store文件夹,并创建store和reducer

// index.js
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store
// reducer.js 用于更新状态。dispatch发过来的action都会通过这里来改变store状态
// state参数起始存放的是原始数据
// action参数是一个对象,由store传递进来的数据变更
const reducer = (state, action) => {
  return state
}
export default reducer

用一个代码片段来实现流程

// 导入建好的store
import store from './store'
class App extends Component {
  constructor (props) {
    super(props)
    this.state = {
      name: store.getState().name || 'Aerith',
    }
    // 订阅来自store的通知,有通知则出发storeChange函数
    // 这个订阅会返回一个函数,可以用来解除订阅
    this.unsubscribe = store.subscribe(this.storeChange)
  }
  storeChange = () => {
    this.setState({
      name: store.getState().name
    })
  }
  changeValue = name => {
    store.dispatch({
      type: 'change_name',
      value: name
    })
  }
  componentWillUnmount () {
    this.unsubsribe() // 解除订阅
  }
  render () {
    const goddess = 'Tifa'
    const { name } = this.state
    return (
      <>        
        <h3>Redux</h3>
        <p>{ name }</p>
        <input type="button" value="点我改变名字" onClick={ () => this.changeValue(goddess) } />
      </>
    )
  }
}
// 给state一个初始值 
const reducer = (state={}, action) => {
  if (action.type === 'change_name') {
  	// 克隆一个state的副本
    const newState = JSON.parse(JSON.stringify(state))
    newState.name = action.value
    return newState
  }
  return state
}

2. 待补充...

(HOOK)