React

特点:

  • 采用组件化的模式、声明式编码,提高开发效率及组件复用率
  • 使用虚拟rom + 优秀的Diffing算法,尽量减少与真实DOM的交互

格式:

<!doctype html>
<html lang="zh-cn">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <script type="text/javascript" src="./js/react.development.js"></script>
  <script type="text/javascript" src="./js/react-dom.development.js"></script>
  <script type="text/javascript" src="./js/babel.min.js"></script>
  <script type="text/babel">
    
  </script>
</head>
<body>
<div id="test">

</div>
</body>
</html>

Hello world

<div id="test">

</div>
<script type="text/babel">
  const vdom = <h1>hello</h1>
  ReactDOM.render(vdom, document.getElementById('test'))
</script>

1.1.1. JSX

全称: JavaScript XML

react定义的一种类似于XMLJS扩展语法,规则

  • 定义虚拟dom时不能有引号
  • 标签中混入js表达式时使用{}
  • 样式的类名使用className
  • 内联样式名称小驼峰
  • 不能有多个根标签
  • 标签必须闭合
  • 标签首字母小写开头,则将会转为同名元素,如果html中没有同名标签将会保存。如果开头是大写字母,React将会渲染对应的组件
<script type="text/babel">
  const myId = 'AbCd';
  const data = "这是内容"
  const style = {
    border: '10px solid black',
    borderRadius: 10 + 'px'
  }
  const vdom = (
    <div className="box" style={style}>
      <span style={{color: 'white', 'fontSize': 30 + 'px'}}>文字</span>
    </div>
  );
  ReactDOM.render(vdom, document.getElementById('test'))
</script>
<style>
  .box {
    width: 100px;
    height: 100px;
    background-color: red;
  }
</style>

数组自动遍历

react自动遍历数组,如果想要在页面上显示需要将数组做一个映射:

<script type="text/babel">
  const data = ["你好", "世界", "解决"]
  const vdom = (
    <div>
      <h1>56555</h1>
      <ul>
        {data.map((it, i) => <li key={i}>{it}</li>)}
      </ul>
    </div>
  )
  ReactDOM.render(vdom, document.getElementById("test"))
</script>

函数组件

<script type="text/babel">
  const Com = () => {
    return <h1>66666</h1>
  }
  ReactDOM.render(<Com/>, document.getElementById("test"))
</script>

组件名必须首字母大写

类组件

  <script type="text/babel">
    class MyCom extends React.Component {
      render() {
        return (
          <div>
            <h1>6666</h1>
          </div>
        )
      }
    }
    ReactDOM.render(<MyCom/>, document.getElementById("test"))
  </script>

有状态的组件称为复杂组件,无状态的组件成为简单组件

组件实例的三大核心:

  • state
  • props
  • refs

state

可以保存状态:

<script type="text/babel">
  class MyCom extends React.Component {
    constructor(props) {
      super(props);
      this.state = {flag: false}
    }

    render() {
      const {flag} = this.state;
      return (
        <div>
          <h1>你好,{flag ? '世界' : '天空'}</h1>
        </div>
      );
    }
  }

  ReactDOM.render(<MyCom/>, document.getElementById("test"))
</script>

事件

原生的三种绑定事件的方式:

  <script type="text/javascript">
    window.onload = () => {
      const btn1 = document.getElementById("btn1");
      btn1.addEventListener('click', () => alert("111"))
      const btn2 = document.getElementById("btn2");
      btn2.onclick = () => alert("222")
    }
    let b3 = () => alert("333")
  </script>
<button id="btn1">11111</button>
<button id="btn2">22222</button>
<button onclick="b3()">33333</button>

React中推荐使用第3种

setState

分为对象式和函数式

对象式

setState用来更新页面上的数据,构造器只调用一次,render调用多次,每次调用setState都会调用一次render,是异步更新的,还可以指定一个更新后的回调,回调是可选的

plus = () => {
  let {count} = this.state;
  count++;
  this.setState({count}, () => {
    console.log(this.state)
  })
}

类中定义的方法都开启了严格模式,也就是此时哪个对象调用方法,哪个对象就是方法中的this的指向,可以通过bind(Object)函数返回一个更改this指向后的新函数

  <script type="text/babel">
    class MyCom extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          flag: false,
          content: "内容"
        }
        this.click = this.click.bind(this);
      }

      render() {
        const {flag, content} = this.state;
        return (
          <div>
            <h1 onClick={this.click}>你好,{flag ? '世界' : '天空'}--{content}</h1>
          </div>
        );
      }

      click() {
        const {flag} = this.state;
        // 状态不能直接更改
        // this.state.flag = !this.state.flag;
        console.log(this);
        // 状态必须通过setState更改,是合并,而不是替换
        this.setState({flag: !flag})
        console.log(this.state);
      }
    }

    ReactDOM.render(<MyCom/>, document.getElementById("test"))
  </script>

精简代码:

可以通过以下方式在一个类上追加一个属性,效果等同于在构造器中使用this.xxx = xxx

class Car {
  a = 1
}

可以把方法写成箭头函数的样子,因为箭头函数没有自己的this,箭头函数的this取决于它的外部,类中的箭头函数中的this就是这个类的实例对象

所以精简形式为:

<script type="text/babel">
  class MyCom extends React.Component {
    state = {
      flag: true,
      content: "天"
    }

    render() {
      const {flag, content} = this.state;
      return (
        <div>
          <h1 onClick={this.click}>你好,{flag ? '世界' : '天空'}--{content}</h1>
        </div>
      );
    }

    click = () => {
      const {flag} = this.state
      this.setState({flag: !flag})
      console.log(this.state);
    }
  }

  ReactDOM.render(<MyCom/>, document.getElementById("test"))
</script>
函数式

this.setState(onchange, [callback])

  • onchange可以接收两个参数,第一个参数为state,第二个参数为props,它的返回值将作为state中设置的值
  • callback是可选的
plus = () => {
  this.setState((state, props) => {
    return {
      count: state.count + 1
    }
  }, () => {
    console.log(this.state)
  })
}

props

基本的用法, 直接在组件身上写属性即可:

<script type="text/babel">
  const obj = {
    name: "aaaa",
    age: 12,
    size: 100
  }

  class MyCom extends React.Component {
    state = {
      count: 1
    }

    render() {
      const {name} = this.props;
      console.log(this.props);
      console.log(name)
      return (
        <div>
          <ul>
            <li>姓名:{name.name}</li>
            <li>年龄:{name.age}</li>
            <li>大小:{name.size}</li>
          </ul>
        </div>
      );
    }
  }

  ReactDOM.render(<MyCom good="你好" test="测试" name={obj}/>, document.getElementById("test"))
</script>

...运算符可以用来展开数组、合并数组、可变参数、对象的深拷贝:

    const arr = ["aa", "bb", "cc"]
    console.log(...arr)
// -----------------------------
    const arr = ["aa", "bb", "cc"]
    const arr2 = ["11", "22", "33"]
    const b = [...arr, ...arr2]
    console.log(b)
// -----------------------------
    const fun = (...a) => {
      console.log(a);
    }
    fun(1, 2, 3, 5, 6)
// -----------------------------
    const p = {
      id: "6666",
      age: 7777
    }
    const p2 = {...p}
    console.log(p2)
    console.log({...p})
    // 还可以在深拷贝对象时修改、合并属性
    const ppp = {...p, id: 5}

原生JavaScript不允许直接使用...展开一个对象,即:...obj是错误的,但React + Babel可以,但仅仅可以在标签上使用,其他地方不能够随意使用:

ReactDOM.render(<MyCom {...obj}/>, document.getElementById("test"))

此时的obj中的所有属性将会直接放到props

对props进行限制

可以对类型显示,需要引入prop-types.js

<script type="text/babel">
  const obj = {
    name: "aaaa",
    age: 12,
    fun() {
      return 1
    }
  }

  class MyCom extends React.Component {
    state = {
      count: 1
    }

    render() {
      const {name, age, size} = this.props;
      return (
        <div>
          <ul>
            <li>姓名:{name}</li>
            <li>年龄:{age}</li>
            <li>大小:{size}</li>
          </ul>
        </div>
      );
    }
  }

  MyCom.propTypes = {
    // 这个属性必须要有    
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    // 这个属性是函数,因为function是关键字,为了避免冲突
    fun: PropTypes.func
  }
  MyCom.defaultProps = {
    size: 10000
  }

  ReactDOM.render(<MyCom {...obj}/>, document.getElementById("test"))
</script>

props是只读的

在以上代码中可以看出propTypesdefaultProps是给这个类加的属性,所以可以将这两个属性移动到类中,并用static修饰

  <script type="text/babel">
    const obj = {
      name: "aaaa",
      age: 12,
      fun() {
        return 1
      }
    }

    class MyCom extends React.Component {
      state = {
        count: 1
      }

      static propTypes = {
        name: PropTypes.string.isRequired,
        age: PropTypes.number,
        fun: PropTypes.func
      }

      static defaultProps = {
        size: 10000
      }

      render() {
        const {name, age, size} = this.props;
        return (
          <div>
            <ul>
              <li>姓名:{name}</li>
              <li>年龄:{age}</li>
              <li>大小:{size}</li>
            </ul>
          </div>
        );
      }
    }


    ReactDOM.render(<MyCom {...obj}/>, document.getElementById("test"))
  </script>

函数组件使用props

函数组件仅能用props

<script type="text/babel">
  const obj = {
    name: "名称",
    age: 12,
    sex: "男"
  }

  let MyCom = (props) => {
    const {name, age, sex} = props;
    return (
      <div>
        <ul>
          <li>名称:{name}</li>
          <li>年龄:{age}</li>
          <li>性别:{sex}</li>
        </ul>
      </div>
    )
  }
  MyCom.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    sex: PropTypes.string
  }
  MyCom.defaultProps = {
    age: 1000
  }
  ReactDOM.render(<MyCom {...obj}/>, document.getElementById("test"))
</script>

refs

将实际的dom元素挂载到this.refs对象上

字符串ref

ref指定的名称为字符串,这种方式不推荐,存在着效率问题,可能会弃用

    class MyCom extends React.Component {
      content = '';
      getContent = () => {
        const {input1} = this.refs;
        alert(input1.value)
      }

      getContent2 = () => {
        const {input2} = this.refs;
        alert(input2.value);
      }

      render() {
        return (
          <div>
            <input ref="input1" type="text"/>
            <br/>
            <br/>
            <button onClick={this.getContent}>提交</button>
            <br/>
            <br/>
            <input ref="input2" onBlur={this.getContent2} type="text"/>
          </div>
        )
      }
    }

    ReactDOM.render(<MyCom />, document.getElementById("test"))

回调函数形式ref

可以直接挂载到实例身上:

    class MyCom extends React.Component {
      content = '';
      getContent = () => {
        const {input1} = this;
        alert(input1.value)
      }

      getContent2 = () => {
        const {inp} = this;
        alert(inp.value);
      }

      render() {
        return (
          <div>
            <input ref={c => this.input1 = c} type="text"/>
            <br/>
            <br/>
            <button onClick={this.getContent}>提交</button>
            <br/>
            <br/>
            <input ref={c => this.inp = c} onBlur={this.getContent2} type="text"/>
          </div>
        )
      }
    }

    ReactDOM.render(<MyCom />, document.getElementById("test"))

React.createRef()方法创建ref

每个通过React.createRef()创建的ref容器只能为一个标签绑定,多次绑定将会替换

class MyCom extends React.Component {
  myInputRef = React.createRef();
  state = {
    flag: false
  }

  getValue = () => {
    alert(this.myInputRef.current.value)
  }

  render() {
    return (
      <div>
        <input ref={this.myInputRef} type="text"/>
        <button onClick={this.getValue}>获取值</button>
      </div>
    )
  }
}

ReactDOM.render(<MyCom />, document.getElementById("test"))

事件

  1. 通过onXxx属性指定事件处理函数(注意大小写)
  2. React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
  3. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  4. 通过event.target得到发生事件的DOM元素对象

不要过度的使用ref,当发生事件的元素就是为了进行操作的元素可以省略ref,从而直接通过事件中的target获取到这个元素中的dom

class MyCom extends React.Component {
  myInputRef = React.createRef();
  state = {
    flag: false
  }

  getValue = (e) => {
    // alert(this.myInputRef.current.value)
    console.log(e.target);
  }

  render() {
    return (
      <div>
        <input ref={this.myInputRef} type="text"/>
        <button onClick={this.getValue}>获取值</button>
      </div>
    )
  }
}

ReactDOM.render(<MyCom />, document.getElementById("test"))

非受控组件

组件中的数据是现用现取,之前写的组件都属于非受控组件,例如:

class MyCom extends React.Component {
  login = (e) => {
    console.log(this);
    alert("username = " + this.username.value + "\npassword = " + this.password.value);
  }

  render() {
    return (
      <div>
        用户名:<input ref={c => this.username = c} type="text" name="username"/>
        <br/>
        密码:<input ref={c => this.password = c} type="text" name="username"/>
        <br/>
        <button onClick={this.login}>登录</button>
      </div>
    )
  }
}

ReactDOM.render(<MyCom/>, document.getElementById("test"))

受控组件

随着内容的输入,将会把内容更改到state中,等需要时,再从状态中取出,这种组件称为受控组件,使用受控组件可以减少ref的使用,React建议不要过度使用ref

以下组件就是受控组件:

class MyCom extends React.Component {
  state = {
    username: '',
    password: ''
  }
  login = (e) => {
    const {username, password} = this.state;
    alert(`username: ${username}\npassword: ${password}`)
  }

  changeUsername = (e) => {
    this.setState({username: e.target.value})
  }
  changePassword = (e) => {
    this.setState({password: e.target.value})
  }


  render() {
    const {username, password} = this.state;
    return (
      <div>
        用户名:<input onChange={this.changeUsername}/>
        <br/>
        密码:<input onChange={this.changePassword} type="password"/>
        <br/>
        <button onClick={this.login}>登录</button>
        <br/>
        你输入的信息:<br/>
        <p>username:{username}</p>
        <p>password:{password}</p>
      </div>
    )
  }
}

ReactDOM.render(<MyCom/>, document.getElementById("test"))

高阶函数

如果一个函数a符合以下条件中的任意一个,那么该函数为高阶函数:

  • 接受的参数为函数
  • 返回值为函数

函数的柯里化:通过函数调用继续返回函数的方式

可以发现之前的例子中需要为每个表单项都要提供一个事件改变的函数,可以使用函数柯里化统一管理:

class MyCom extends React.Component {
  state = {
    username: '',
    password: ''
  }
  login = (e) => {
    const {username, password} = this.state;
    alert(`username: ${username}\npassword: ${password}`)
  }

  dataChange = (name) => {
    return (e) => {
      this.setState({
        [name]: e.target.value
      })
    }
  }


  render() {
    const {username, password} = this.state;
    return (
      <div>
        用户名:<input onChange={this.dataChange('username')}/>
        <br/>
        密码:<input onChange={this.dataChange('password')} type="password"/>
        <br/>
        <button onClick={this.login}>登录</button>
        <br/>
        你输入的信息:<br/>
        <p>username:{username}</p>
        <p>password:{password}</p>
      </div>
    )
  }
}

ReactDOM.render(<MyCom/>, document.getElementById("test"))

生命周期(16)

组件挂载时的生命周期:

  1. 构造器先执行
  2. componentWillMount(),组件挂载前
  3. render(),组件渲染
  4. componentDidMount()会在组件挂载完毕后调用,由于这个函数是由对象实例调用的,所以this的指向就是实例,无需写成箭头函数的形式
  5. componentWillUnmount()组件卸载前调用
class MyCom extends React.Component {
  state = {
    opacity: 1
  }

  remove = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById('test'));
  }

  componentDidMount() {
    this.start = setInterval(() => {
      let {opacity} = this.state;
      opacity -= 0.1;
      if (opacity <= 0) {
        opacity = 1;
      }
      this.setState({
        opacity
      })
    }, 200);
  }

  componentWillUnmount() {
    clearInterval(this.start)
  }

  render() {
    const {opacity} = this.state;
    return (
      <div>
        <p style={{
          opacity
        }}>123456789</p>
        <button onClick={this.remove}>消失</button>
      </div>
    );
  }
}

ReactDOM.render(<MyCom/>, document.getElementById("test"))

调用setState()方法,组件更新时的生命周期:

  1. shouldComponentUpdate()组件是否要更新,返回值为布尔值,如果为false,以下回调将不会执行,页面也不会渲染
  2. componentWillUpdate()组件将要更新了
  3. render()渲染页面
  4. componentDidUpdate()组件更新后
class MyCom extends React.Component {
  constructor(props) {
    super(props);
    console.log("m-构造器执行..")
  }

  remove = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById('test'));
  }

  componentWillMount() {
    console.log("m-组件将要挂载...")
  }

  componentDidMount() {
    console.log("m-组件挂载....")
  }

  componentWillUnmount() {
    console.log("m-组件将要卸载...")
  }

  shouldComponentUpdate() {
    console.log("m-组件是否需要更新...")
    return true;
  }

  componentWillUpdate() {
    console.log("m-组件将要更新了...")
  }

  componentDidUpdate() {
    console.log("m-组件更新了...")
  }

  state = {
    count: 0
  }

  add = () => {
    this.setState({count: this.state.count + 1})
  }

  render() {
    console.log("m-render执行...")
    const {count} = this.state;
    return (
      <div>
        <h1>{count}</h1>
        <button onClick={this.add}>+1</button>
        <br/>
        <br/>
        <button onClick={this.remove}>卸载</button>
      </div>
    );
  }
}

ReactDOM.render(<MyCom/>, document.getElementById("test"))

forceUpdate()

force中文为强迫、强制、力量

强制更新可以在数据不进行改变时进行更新,一系列的回调:

  1. componentWillUpdate()组件将要更新了
  2. render()渲染页面
  3. componentDidUpdate()组件更新后

也就是不经过shouldComponentUpdate()的控制

class MyCom extends React.Component {
  constructor(props) {
    super(props);
    console.log("m-构造器执行..")
  }

  remove = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById('test'));
  }

  componentWillMount() {
    console.log("m-组件将要挂载...")
  }

  componentDidMount() {
    console.log("m-组件挂载....")
  }

  componentWillUnmount() {
    console.log("m-组件将要卸载...")
  }

  shouldComponentUpdate() {
    console.log("m-组件是否需要更新...")
    return true;
  }

  componentWillUpdate() {
    console.log("m-组件将要更新了...")
  }

  componentDidUpdate() {
    console.log("m-组件更新了...")
  }

  update = () => {
    console.log("m-点击了强制更新按钮...");
    this.state.count += 10;
    this.forceUpdate();
  }

  state = {
    count: 0
  }

  add = () => {
    this.setState({count: this.state.count + 1})
  }

  render() {
    console.log("m-render执行...")
    const {count} = this.state;
    return (
      <div>
        <h1>{count}</h1>
        <button onClick={this.add}>+1</button>
        <br/>
        <br/>
        <button onClick={this.remove}>卸载</button>
        <button onClick={this.update}>强制更新</button>
      </div>
    );
  }
}

父子组件的更新时的回调:

  1. 父组件shouldComponentUpdate()
  2. 父组件componentWillUpdate()
  3. 父组件render()
  4. 子组件componentWillReceiveProps()(如果有传的Props
  5. 子组件shouldComponentUpdate()
  6. 子组件componentWillUpdate()
  7. 子组件render()
  8. 子组件componentDidUpdate()
  9. 父组件componentDidUpdate()

componentDidUpdate()可以带有两个参数,分别是之前的props和之前的state

componentDidUpdate(preProps, PreState) {
  console.log("son-组件更新了", preProps, PreState)
}
class Father extends React.Component {
  state = {
    content: "你好"
  }
  index = 0;
  arr = ['你好', '我好', '大家好']

  updateFather = () => {
    this.index++;
    this.index %= this.arr.length;
    this.setState({content: this.arr[this.index]})
  }

  render() {
    console.log("Father-渲染页面")
    const {content} = this.state;
    return (
      <div>
        <p>父组件中此时的内容:{content}</p>
        <br/>
        <button onClick={this.updateFather}>修改父组件</button>
        <br/>
        <br/>
        <Son content={content}/>
      </div>
    );
  }

  shouldComponentUpdate() {
    console.log("Father-组件是否应该更新")
    return true;
  }

  componentWillUpdate() {
    console.log("Father-组件准备更新")
  }

  componentDidUpdate() {
    console.log("Father-组件更新了")
  }
}

class Son extends React.Component {
  render() {
    console.log("son-渲染页面")
    return (
      <div>
        <p>子组件此时的内容为:{this.props.content}</p>
      </div>
    );
  }

  componentWillReceiveProps() {
    console.log("son-接收到Props了")
  }

  shouldComponentUpdate() {
    console.log("son-组件是否应该更新")
    return true;
  }

  componentWillUpdate() {
    console.log("son-组件准备更新")
  }

  componentDidUpdate() {
    console.log("son-组件更新了")
  }
}

ReactDOM.render(<Father/>, document.getElementById("test"))

常用的有3个:

  • componentDidMount(),一般做初始化,例如开始定时器、发送网络请求、订阅消息
  • render(),必须要用的
  • componentWillUnmount(),一般做一些收尾的事,例如:关闭定时器、取消消息订阅

生命周期(17)

所有带有will单词的回调方法都推荐添加UNSAFE_前缀,并且不再推荐使用了

derived中文为衍生,dɪˈraɪvd

getDerivedStateFromProps,这个方法必须为静态的,返回值要么是一个状态对象(可以认为一个state对象),要么是null,当为一个状态对象时,无论再怎么调用setState()将无法继续修改值,即使调用forceUpdate()也无法强制更新,可以接收到一个props对象和一个state对象,如果state对象没有被赋值,控制台会看到错误,每当数据更新时,这个方法中的state为旧值

应用场景:state的值在任何时候都取决于props

static getDerivedStateFromProps(props) {
  console.log("father-getDerivedStateFromProps");
  return {
    content: "儿子"
  }
}
      static getDerivedStateFromProps(props, state) {
        console.log("props = ", props);
        console.log("state = ", state);
        return props;
      }

getSnapshotBeforeUpdate(),返回值为null或者一个值,返回的值会被componentDidUpdate()的第三个参数接收:

getSnapshotBeforeUpdate(preProps, preState) {
  console.log("Father-getSnapshotBeforeUpdate", preProps, preState);
  return "你好,我是getSnapshotBeforeUpdate()传递的值";
}

componentDidUpdate(preProps, preState, snapshot) {
  console.log("Father-组件更新了", preProps, preState, snapshot);
}

应用场景:

每隔一定的时间追加一定的内容,而原有的内容不随着滚动

class News extends React.Component {
  state = {
    arr: []
  }

  componentDidMount() {
    setInterval(() => {
      const {arr} = this.state;
      const content = `内容${arr.length + 1}`
      this.setState({
        arr: [content, ...arr]
      })
    }, 1000)
  }

  getSnapshotBeforeUpdate() {
    return this.box.scrollHeight;
  }

  componentDidUpdate(preProps, preState, snapshot) {
    this.box.scrollTop += this.box.scrollHeight - snapshot;
  }

  render() {
    const {arr} = this.state;
    return (
      <div ref={(t) => this.box = t} className="box">
        {arr.map((it, index) => <div key={index} className="item">{it}</div>)}
      </div>
    );
  }
}

ReactDOM.render(<News/>, document.getElementById("test"))

Diffing算法

如果有值通过setState进行改变,他只会将改变的那个标签进行更新

key的用法:

  • 在数据改变时,如果新的虚拟dom和旧的虚拟dom有相同的key,如果有将会对比两者的内容
    • 如果内容改变了,用新的的虚拟dom替换真实dom
    • 如果没有改变,直接用之前旧的真实dom
  • 如果没有相同的key,将会根据数据创建新的dom渲染到页面

脚手架

生成的index.html文件中头部信息内容含义:

<!--    %PUBLIC_URL%代表public这个文件夹-->
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
<!--    用来配置浏览器页签和地址栏的颜色,仅支持Android系统-->
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
<!--    将网站图标添加到桌面时的图标-->
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--    pwa使用-->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

index.js中的:

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

作用是检查React子组件中用法是否合理,是否用过不合理的api


以下用于记录页面的性能:

import reportWebVitals from './reportWebVitals';
reportWebVitals();

最基本的结构:

import React from "react";

class App extends React.Component {
  render() {
    return (
        <div>
          hello
        </div>
    );
  }
}
export default App;
import {Component} from "react"; // 采用的分别暴露方式引入的

class App extends Component {
  render() {
    return (
        <div>
          hello666
        </div>
    );
  }
}
export default App;

继续优化:

import {Component} from "react";

export default class App extends Component {
  render() {
    return (
        <div>
          hello666
        </div>
    );
  }
}

CSS模块化

css文件命名时按照文件名.module.css

使用:

import {Component} from "react";
import hello from "../css/my.module.css"

export default class Hello extends Component {
  render() {
    return (
      <div id={hello.my}>
        你好
      </div>
    );
  }
}

代码片段

rsf函数组件快速生成,rcc快速生成类式组件,sst生成this.setState();

propTypes

对传递的类型限制,需要安装:

 npm install prop-types

引入:

import PropTypes from "prop-types";

消息的订阅和发布

解决兄弟组件之间的通信,使用pubsubjs

 npm install pubsub-js

接收消息的一方在生命周期componentDidMounted回调中指定:

PubSub.unsubscribe('MY TOPIC');
PubSub.subscribe('MY TOPIC', (name, data) => {});

发送消息的一方使用:PubSub.publish('MY TOPIC', {消息});发送消息

Fetch

中文拿、取,Fetch是使用Promise开发的,基本用法:

fetch('https://up.api.daidr.me/apis/hitokoto.json')
  .then(res => res.json())
  .then(data => console.log(data))

async-await

const response = await fetch('https://up.api.daidr.me/apis/hitokoto.json');
const result = await response.json();
console.log(result);

路由

使用的React-router-dom库,是官方维护的路由库

 npm install react-router-dom
  • 需要使用<Link to="路径" />标签代替<a />标签
  • <Link/>标签需要放到Router标签中,Router标签有多个
    • 可以使用<BrowserRouter></BrowserRuter>标签包裹起来
    • 也可以使用<HashRouter></HashRouter>
  • 使用<Route path="路径" component={组件} />指定某个路径所对应的组件
  • Route也需要放到一个Router标签下
  • 如果RouteLink标签不在同一个Router标签下时,当点击了Link标签时,不会引起页面内容变化,只能刷新页面才能够看到变化
  • 可以将App包裹在一个Router下,这样可以实现所有的RouteLink都在同一个Router标签下了
  • 只需要在index.js中进行包裹即可

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter} from "react-router-dom";
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App/>
  </BrowserRouter>
);

app.js

import {Component} from "react";
import {Link, Route} from "react-router-dom";
import Home from "./component/Home";
import About from "./component/About";
import './App.css';


export default class App extends Component {

  render() {
    return (
      <div>
        <Link to={'/home'}>home</Link>
        <br/>
        <Link to={'/about'}>about</Link>

        <div className="content">
          <Route path={'/home'} component={Home}/>
          <Route path={'/about'} component={About}/>
        </div>
      </div>
    );
  }
}

路由组件:<Route path={'/home'} component={Home}/>

一般组件:<Home></Home>

路由组件

路由组件中的props中会带有以下信息:

{
    "history": {
        "length": 7,
        "action": "POP",
        "location": {
            "pathname": "/home/a",
            "search": "",
            "hash": ""
        }
    },
    "location": {
        "pathname": "/home/a",
        "search": "",
        "hash": ""
    },
    "match": {
        "path": "/home",
        "url": "/home",
        "isExact": false,
        "params": {}
    }
}

作用效果和<Link/>相同,Link标签将会被转换到<a>标签

<NavLink to={'/home'}>home</NavLink>
<br/>
<NavLink to={'/about'}>about</NavLink>

如果当前路由匹配这个NavLinkto属性写的路径,那么默认情况下浏览器中的这个标签将会追加aria-current="page" class="active"属性

可以添加activeClassName={'名称'}属性实现自定义路由匹配时所添加的class

this.props.children

使用这种方式嵌入的组件可以在this.props.children中获取到

<Father>
  <Son />
</Father>
import React, {Component} from 'react';

class Father extends Component {
  render() {
    console.log(this)
    const {children} = this.props;
    return (
      <div>
        父亲
        <div>
          {children}
        </div>
      </div>
    );
  }
}

export default Father;

Switch组件

如果一个路径中对应了多个组件,那么两个组件都显示

<Route path={'/home'} component={Home}/>
<Route path={'/about'} component={About}/>
<Route path={'/about'} component={Test} />

这就代表着即使从上到下扫描后匹配到了组件,仍会继续向下扫描继续匹配,如果组件特别多将会存在效率上的问题,使用switch组件解决此问题

<Switch>
  <Route path={'/home'} component={Home}/>
  <Route path={'/about'} component={About}/>
  <Route path={'/about'} component={Test} />
</Switch>

匹配

默认情况下是模糊匹配,<Route path={'/home'} component={Home}/>中,/home/home/aa/n等都能匹配上

添加exact属性实现精准匹配,iɡˈzakt,中文为准确的,<Route exact={true} path={'/about'} component={About}/>或者<Route exact path={'/about'} component={About}/>

重定向

可以设置某个路径下的默认的一个路由

<Switch>
  <Route path={'/home'} component={Home}/>
  <Route path={'/about'} component={About}/>
  <Redirect to={'/home'} />
</Switch>

<Redirect/>进行兜底,卸载最下边

接收参数

可以采用:名称作为占位符,子组件中可以通过this.props.match.params获取占位符中的参数

class Home extends Component {
  state = {
    arr: [
      {
        id: 1,
        title: "标题",
        content: "这是内容"
      },
      {
        id: 2,
        title: "标题222",
        content: "这是内容2222"
      },
      {
        id: 3,
        title: "标题333",
        content: "这是内容333"
      },
    ]
  }

  render() {
    const {arr} = this.state;
    return (
      <div className={"my-home"}>
        <ul>
          {
            arr.map(it =>
              <li key={it.id}>
                <Link to={`/home/message/${it.id}`}>消息{it.id}</Link>
              </li>)
          }
        </ul>
        <div
          style={{
            border: "1px solid black",
            margin: "auto",
            width: '250px',
            height: '200px',
            color: 'black'
          }}>
          <p>消息区</p>
          <Route path={'/home/message/:id'} component={Message} />
        </div>
      </div>
    );
  }
}

export default Home;
class Message extends Component {
  componentDidMount() {
    console.log(this.props.match.params);
  }

  render() {
    const {id, title, content} = this.props.match.params;
    return (
      <div>
        <p>id:{id}</p>
        <p>title:{title}</p>
        <p>content:{content}</p>
      </div>
    );
  }
}

export default Message;

search参数

也就是query参数,可以使用第三方库解析

npm install query-string
import qs from "query-string";
console.log(qs.parse(this.props.location.search));
<div className={"my-home"}>
  <ul>
    {
      arr.map(it =>
        <li key={it.id}>
          <Link to={`/home/message/?id=${it.id}&title=${it.title}&content=${it.content}`}>消息{it.id}</Link>
        </li>)
    }
  </ul>
  <div
    style={{
      border: "1px solid black",
      margin: "auto",
      width: '250px',
      height: '200px',
      color: 'black'
    }}>
    <p>消息区</p>
    <Route path={'/home/message'} component={Message} />
  </div>
</div>

node.js自带了qs模块,可以直接使用这个模块:

import React, {Component} from 'react';
import qs from "qs";

class Message extends Component {
  componentDidMount() {
    // 开头会包含?,所以要取子串
    console.log(qs.parse(this.props.location.search.substring(1)));
  }

  render() {
    const {id, title, content} = this.props.match.params;
    return (
      <div>
        <p>id:{id}</p>
        <p>title:{title}</p>
        <p>content:{content}</p>
      </div>
    );
  }
}

export default Message;
render() {

  const {id, title, content} = qs.parse(this.props.location.search.substring(1))
  return (
    <div>
      <p>id:{id}</p>
      <p>title:{title}</p>
      <p>content:{content}</p>
    </div>
  );

向路由传递state参数

如果使用queryparam的方式进行传递时,传递的参数将会暴露在地址栏中

<li key={it.id}>
  <Link to={{
    pathname: '/home/message',
    state: it
  }}>消息{it.id}</Link>
</li>)

接收:

const {id, title, content} = this.props.location.state;

如果使用的是<BrowserRouter>时,刷新页面时内容不丢失,如果清空缓存将会丢失,为了避免这种情况,可以将写法变为:

const {id, title, content} = this.props.location.state || {};

replace

默认情况下使用的是push方式,可以切换为replace方式,一个保留历史记录,一个不保留

<NavLink replace activeClassName={'aaa'} to={'/home'}>home</NavLink>
<br/>
<NavLink replace activeClassName={'aaa'} to={'/about'}>about</NavLink>

方法调用方式跳转路由

可以通过调用方法进行跳转,queryparam可以通过拼接字符串实现:

this.props.history.push('/about')
this.props.history.replace('/about')

参数2可以填写state

this.props.history.push('/home/message', {
  id: '8888',
  title: '565656566',
  content: '9999999'
})

this.props.history.replace('/home/message', {
  id: '8888',
  title: '565656566',
  content: '9999999'
})
<button onClick={() => this.props.history.goBack()}>后退</button>
<button onClick={() => this.props.history.goForward()}>前进</button>

withRouter

只有路由组件才能使用路由中的 this.props.xxx方法和属性

路由组件:<Route path={'/home'} component={Home}/>

一般组件:<Home></Home>

为了解决此问题,引入了withRouter,返回值是一个新组件

import {withRouter} from "react-router-dom";


class App extends Component {

  render() {
    return (
      <div>
        <button onClick={() => this.props.history.goBack()}>后退</button>
        <button onClick={() => this.props.history.goForward()}>前进</button>
      </div>
    );
  }
}
export default withRouter(App);

Redux

是一个专门做状态管理的JS库 ,集中管理应用中共享的状态,所有共享的数据都可以交给Redux管理,需要用到的情况:

  • 某个组件的状态,需要让其他组件可以随时拿到(共享)
  • 一个组件需要改变另一个组件的状态(通信)
  • 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
 npm install redux

精简版

一个计算器的案例:

import {Component} from "react";
import {Button, ConfigProvider} from 'antd'
import {LogoutOutlined} from '@ant-design/icons';
import {withRouter} from "react-router-dom";
import './App.css';


class App extends Component {
  state = {
    sum: 0
  }

  plus = () => {
    let {sum} = this.state;
    sum += Number.parseInt(this.select.value);
    this.setState({sum})
  }
  plusOdd = () => {
    let {sum} = this.state;
    if (sum % 2 !== 0) {
      sum += Number.parseInt(this.select.value);
      this.setState({sum})
    }
  }
  subtract = () => {
    let {sum} = this.state;
    sum -= Number.parseInt(this.select.value);
    this.setState({sum})
  }

  plusAsync = () => {
    setTimeout(() => {
      let {sum} = this.state;
      sum += Number.parseInt(this.select.value);
      this.setState({sum})
    }, 100);
  }

  render() {
    const {sum} = this.state;
    return (
      <div>
        <p>{sum}</p>
        <select ref={e => this.select = e}>
          <option value='1'>1</option>
          <option value='2'>2</option>
          <option value='3'>3</option>
        </select>
        <br/>
        <br/>
        <button onClick={this.plus}>+</button>
        <br/>
        <br/>
        <button onClick={this.subtract}>-</button>
        <br/>
        <br/>
        <button onClick={this.plusOdd}>奇+</button>
        <br/>
        <br/>
        <button onClick={this.plusAsync}>异步+</button>
      </div>
    );
  }
}


export default withRouter(App);

用法:

充当reducer,第一个参数为之前的值,action中为本次传入的值

export default (preState, action) => {
  const {type, data} = action;
  switch (type) {
    case 'add':
      return preState + data;
    case 'subtract':
      return preState - data;
    default:
      // 可以用来初始化
      return 0;
  }
}

包装,充当store:

import {legacy_createStore as createStore} from "redux";
import countReducer from './countReducer';

export default createStore(countReducer);

使用:

import {Component} from "react";
import {withRouter} from "react-router-dom";
import countStore from "./redux/countStore";
import './App.css';


class App extends Component {

  componentDidMount() {
    countStore.subscribe(() => {
      this.setState({});
    })
  }

  plus = () => {
    countStore.dispatch({
      type: 'add',
      data: Number.parseInt(this.select.value)
    })
  }
  plusOdd = () => {
    if (countStore.getState() % 2 === 1) {
      countStore.dispatch({
        type: 'add',
        data: Number.parseInt(this.select.value)
      })
    }
  }
  subtract = () => {
    countStore.dispatch({
      type: 'subtract',
      data: Number.parseInt(this.select.value)
    })
  }

  plusAsync = () => {
    setTimeout(() => {
      countStore.dispatch({
        type: 'add',
        data: Number.parseInt(this.select.value)
      })
    }, 100);
  }

  render() {
    return (
      <div>
        <p>{countStore.getState()}</p>
        <select ref={e => this.select = e}>
          <option value='1'>1</option>
          <option value='2'>2</option>
          <option value='3'>3</option>
        </select>
        <br/>
        <br/>
        <button onClick={this.plus}>+</button>
        <br/>
        <br/>
        <button onClick={this.subtract}>-</button>
        <br/>
        <br/>
        <button onClick={this.plusOdd}>奇+</button>
        <br/>
        <br/>
        <button onClick={this.plusAsync}>异步+</button>
      </div>
    );
  }
}


export default withRouter(App);

完整版

在精简版的案例中没有引入action creators,在完整版中:

  • 一个常量文件,写所有的type中指定的名称
  • 一个action creator文件,用来生成一个action
  • 一个store文件,用来包装一个reducer
  • 一个reducer文件,用来处理运算
import {Component} from "react";
import {withRouter} from "react-router-dom";
import countStore from "./redux/countStore";
import './App.css';
import {addCreator, subtractCreator} from "./redux/countActionCreator";


class App extends Component {

  componentDidMount() {
    countStore.subscribe(() => {
      this.setState({});
    })
  }

  plus = () => {
    countStore.dispatch(addCreator(Number.parseInt(this.select.value)))
  }
  plusOdd = () => {
    if (countStore.getState() % 2 === 1) {
      countStore.dispatch(addCreator(Number.parseInt(this.select.value)))
    }
  }
  subtract = () => {
    countStore.dispatch(subtractCreator(Number.parseInt(this.select.value)))
  }

  plusAsync = () => {
    setTimeout(() => {
      countStore.dispatch(addCreator(Number.parseInt(this.select.value)))
    }, 100);
  }

  render() {
    return (
      <div>
        <p>{countStore.getState()}</p>
        <select ref={e => this.select = e}>
          <option value='1'>1</option>
          <option value='2'>2</option>
          <option value='3'>3</option>
        </select>
        <br/>
        <br/>
        <button onClick={this.plus}>+</button>
        <br/>
        <br/>
        <button onClick={this.subtract}>-</button>
        <br/>
        <br/>
        <button onClick={this.plusOdd}>奇+</button>
        <br/>
        <br/>
        <button onClick={this.plusAsync}>异步+</button>
      </div>
    );
  }
}


export default withRouter(App);

常量文件:

export const ADD = 'add';
export const SUBTRACT = 'subtract';

创建者:

import {ADD, SUBTRACT} from './constant';

export const addCreator = data => ({type: ADD, data});
export const subtractCreator = data => ({type: SUBTRACT, data});

reducer

import {ADD, SUBTRACT} from "./constant";

export default (preState, action) => {
  const {type, data} = action;
  switch (type) {
    case ADD:
      return preState + data;
    case SUBTRACT:
      return preState - data;
    default:
      // 可以用来初始化
      return 0;
  }
}

异步action

action值可以为对象或者函数,当action为对象时称为同步action,如果为函数时称为异步action,需要引入一个中间件转换redux-thunk

npm install redux-thunk

修改store

import {legacy_createStore as createStore, applyMiddleware} from "redux";
import thunk from 'redux-thunk';
import countReducer from './countReducer';

export default createStore(countReducer, applyMiddleware(thunk));

添加createor

export const addAsyncCreator = (data, time) => {
  return dispatch => {
    setTimeout(() => {
      dispatch({
        type: ADD,
        data
      })
    }, time);
  }
}

jsx中的调用:

plusAsync = () => {
  countStore.dispatch(addAsyncCreator(Number.parseInt(this.select.value), 100));
}

在上例中,可以把异步的加法的setTimeout放到一个creator

react-redux

react-redux中可以自动检测数据的变化,无需再使用setState进行手动更新数据

安装:

npm install react-redux

UI组件:

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 通过props接收数据(一般数据和函数)
  • 不使用任何 Redux 的 API
  • 一般保存在components文件夹下

容器组件:

  • 负责管理数据和业务逻辑,不负责UI的呈现
  • 使用 Redux 的 API
  • 一般保存在containers文件夹下

其他的都不变,即reduceractioncreatorstore不用修改,把之前在jsx中用到的redux剥离出来,只用来显示页面,通过容器组件传递props的方法为其修改值

container:

import CountUI from "../component/ui/CountUI";
import {connect} from "react-redux";
import {addAsyncCreator, addCreator, subtractCreator} from "../redux/countActionCreator";

// 调用时已经将state传递过来了
const mapStateToProps = (state) => {
  return {
    value: state
  }
}

const mapDispathToProps = dispatch => {
  return {
    add(num) {
      dispatch(addCreator(num));
    },
    subtract(num) {
      dispatch(subtractCreator(num))
    },
    addAsync(num, time) {
      dispatch(addAsyncCreator(num, time));
    }
  }
}


// 参数1为数据,参数2为实际的操作方法
export default connect(mapStateToProps, mapDispathToProps)(CountUI);

ui:

import {Component} from "react";
import {withRouter} from "react-router-dom";

class CountUI extends Component {

  componentDidMount() {
    // console.log(this)
  }

  plus = () => {
    const value = Number.parseInt(this.select.value);
    this.props.add(value);
  }
  plusOdd = () => {
    const value = Number.parseInt(this.select.value);
    if (this.props.value % 2 === 1) {
      this.props.add(value);
    }
  }
  subtract = () => {
    const value = Number.parseInt(this.select.value);
    this.props.subtract(value);
  }

  plusAsync = () => {
    const value = Number.parseInt(this.select.value);
    this.props.addAsync(value, 200);
  }

  render() {
    const {value} = this.props;
    return (
      <div>
        <p>{value}</p>
        <select ref={e => this.select = e}>
          <option value='1'>1</option>
          <option value='2'>2</option>
          <option value='3'>3</option>
        </select>
        <br/>
        <br/>
        <button onClick={this.plus}>+</button>
        <br/>
        <br/>
        <button onClick={this.subtract}>-</button>
        <br/>
        <br/>
        <button onClick={this.plusOdd}>奇+</button>
        <br/>
        <br/>
        <button onClick={this.plusAsync}>异步+</button>
      </div>
    );
  }
}


export default withRouter(CountUI);

app.js

import {Component} from "react";
import {withRouter} from "react-router-dom";
import './App.css';
import CountContainer from "./container/CountContainer";
import countStore from "./redux/countStore";


class App extends Component {

  render() {
    return (
      <div>
        <CountContainer store={countStore} />
      </div>
    );
  }
}


export default withRouter(App);

精简map

mapDispatchProps中,react-redux会默认的使用dispatch进行调用

import CountUI from "../component/ui/CountUI";
import {connect} from "react-redux";
import {addAsyncCreator, addCreator, subtractCreator} from "../redux/countActionCreator";

// 参数1为数据,参数2为实际的操作方法
export default connect(
  (state) => ({
    value: state
  }),
  {
    addCreator,
    subtractCreator,
    addAsyncCreator
  }
)(CountUI);

ui:

import {Component} from "react";
import {withRouter} from "react-router-dom";

class CountUI extends Component {

  componentDidMount() {
    // console.log(this)
  }

  plus = () => {
    const value = Number.parseInt(this.select.value);
    this.props.addCreator(value);
  }
  plusOdd = () => {
    const value = Number.parseInt(this.select.value);
    if (this.props.value % 2 === 1) {
      this.props.addCreator(value);
    }
  }
  subtract = () => {
    const value = Number.parseInt(this.select.value);
    this.props.subtractCreator(value);
  }

  plusAsync = () => {
    const value = Number.parseInt(this.select.value);
    this.props.addAsyncCreator(value, 200);
  }

  render() {
    const {value} = this.props;
    return (
      <div>
        <p>{value}</p>
        <select ref={e => this.select = e}>
          <option value='1'>1</option>
          <option value='2'>2</option>
          <option value='3'>3</option>
        </select>
        <br/>
        <br/>
        <button onClick={this.plus}>+</button>
        <br/>
        <br/>
        <button onClick={this.subtract}>-</button>
        <br/>
        <br/>
        <button onClick={this.plusOdd}>奇+</button>
        <br/>
        <br/>
        <button onClick={this.plusAsync}>异步+</button>
      </div>
    );
  }
}


export default withRouter(CountUI);

Provider标签

如果多个容器使用同一个store,那么可以将这几个容器放到这个标签中:

<Provider store={countStore}>
  <CountContainer />
</Provider>

store是一个单例的

代码片段

  • rrc

    • import React, {Component} from 'react';
      import {connect} from 'react-redux';
      
      function mapStateToProps(state) {
        return {};
      }
      
      class MyCom extends Component {
        render() {
          return (
            <div>
              
            </div>
          );
        }
      }
      
      export default connect(
        mapStateToProps,
      )(MyCom);
      
  • rrdc

    • import React, {Component} from 'react';
      import {connect} from 'react-redux';
      
      function mapStateToProps(state) {
        return {};
      }
      
      function mapDispatchToProps(dispatch) {
        return {};
      }
      
      class MyCom extends Component {
        render() {
          return (
            <div>
              
            </div>
          );
        }
      }
      
      export default connect(
        mapStateToProps,
      )(MyCom);
      

Reducerstore可以合并在一块:

store

import {ADD, SUBTRACT} from "./constant";
import {applyMiddleware, legacy_createStore as createStore} from "redux";
import thunk from "redux-thunk";

const countReducer =  (preState, action) => {
  const {type, data} = action;
  switch (type) {
    case ADD:
      return preState + data;
    case SUBTRACT:
      return preState - data;
    default:
      // 可以用来初始化
      return 0;
  }
}

export default createStore(countReducer, applyMiddleware(thunk));

action:

import {ADD, SUBTRACT} from './constant';

export const addAction = data => ({type: ADD, data});
export const subtractAction = data => ({type: SUBTRACT, data});
export const addAsyncAction = (data, time) => {
  return dispatch => {
    setTimeout(() => {
      dispatch({
        type: ADD,
        data
      })
    }, time);
  }
}

组件:

import {Component} from "react";
import {withRouter} from "react-router-dom";
import {connect} from "react-redux";
import {addAsyncAction, addAction, subtractAction} from "../redux/countAction";

class Count extends Component {

  componentDidMount() {
    // console.log(this)
  }

  plus = () => {
    const value = Number.parseInt(this.select.value);
    this.props.addAction(value);
  }
  plusOdd = () => {
    const value = Number.parseInt(this.select.value);
    if (this.props.value % 2 === 1) {
      this.props.addAction(value);
    }
  }
  subtract = () => {
    const value = Number.parseInt(this.select.value);
    this.props.subtractAction(value);
  }

  plusAsync = () => {
    const value = Number.parseInt(this.select.value);
    this.props.addAsyncAction(value, 200);
  }

  render() {
    const {value} = this.props;
    return (
      <div>
        <p>{value}</p>
        <select ref={e => this.select = e}>
          <option value='1'>1</option>
          <option value='2'>2</option>
          <option value='3'>3</option>
        </select>
        <br/>
        <br/>
        <button onClick={this.plus}>+</button>
        <br/>
        <br/>
        <button onClick={this.subtract}>-</button>
        <br/>
        <br/>
        <button onClick={this.plusOdd}>奇+</button>
        <br/>
        <br/>
        <button onClick={this.plusAsync}>异步+</button>
      </div>
    );
  }
}

export default connect(
  (state) => ({
    value: state
  }),
  {
    addAction,
    subtractAction,
    addAsyncAction
  }
)(withRouter(Count));

多个Reducer

多个reducer可以交给同一个store管理,需要通过combineReducers函数组合在一起

combine中文为组合、混合、合并,读音为kəmˈbaɪn

import {legacy_createStore as createStore, combineReducers} from "redux";
import {personReducer} from "./reducer/personReducer";
import {countReducer} from "./reducer/countReducer";

const allReducer = combineReducers({
  personReducer,
  countReducer
})
export default createStore(allReducer)

此时,代码结构:

  • action文件夹,保存所有的action
  • reducer文件夹,保存所有的reducer
  • constant.js文件,保存所有的常量
  • store.js文件,组合所有的reducer

组件中引入时,引入action的方法和之前一样,但数据需要分别引入:

  • reducer文件夹

    • countReducer

      • import {ADD_COUNT} from "../constant";
        
        
        export const countReducer = (preState = 0, action) => {
          const {type, data} = action;
          switch (type) {
            case ADD_COUNT:
              return preState + data;
            default:
              return preState;
          }
        }
        
    • personReducer

      • import {ADD_PERSON} from "../constant";
        
        
        export const personReducer = (preState = [], action) => {
          const {data, type} = action;
          switch (type) {
            case ADD_PERSON:
              return [...preState, data];
            default:
              return preState;
          }
        }
        
  • action文件夹

    • countAction

      • import {ADD_COUNT} from "../constant";
        
        export const addAction = (data) => {
          return {
            type: ADD_COUNT,
            data
          }
        }
        
    • personAction

      • import {ADD_PERSON} from "../constant";
        
        export const addPersonAction = (data) => {
          return {
            data,
            type: ADD_PERSON
          }
        }
        
  • constant.js文件

    • export const ADD_PERSON = 'addPerson';
      export const ADD_COUNT = 'addCount';
      
  • store.js文件

    • import {legacy_createStore as createStore, combineReducers} from "redux";
      import {personReducer} from "./reducer/personReducer";
      import {countReducer} from "./reducer/countReducer";
      
      const allReducer = combineReducers({
        personReducer,
        countReducer
      })
      export default createStore(allReducer)
      
  • 组件:

    • count

      • import React, {Component} from 'react';
        import {connect} from 'react-redux';
        import {addAction} from "../../redux/action/countAction";
        
        function mapStateToProps(state) {
          return {
            count: state.countReducer,
            size: state.personReducer.length
          };
        }
        
        
        class Count extends Component {
          add = () => {
            this.props.addAction(Number.parseInt(this.c.value));
          }
        
          render() {
            const {count, size} = this.props;
            return (
              <div>
                <p>当前:{count}</p>
                <input ref={e => this.c = e} type="text"/>
                <br/>
                <br/>
                <button onClick={this.add}>增加</button>
                <p>Person人数:{size}</p>
              </div>
            );
          }
        }
        
        export default connect(
          mapStateToProps,
          {
            addAction
          }
        )(Count);
        
        
    • person

      • item

        • import React, {Component} from 'react';
          
          class Item extends Component {
            render() {
              const {name, size, more} = this.props;
              return (
                <div>
                  <p>名称:{name},大小:{size},备注:{more}</p>
                </div>
              );
            }
          }
          
          export default Item;
          
      • manager

        • import React, {Component} from 'react';
          import {connect} from 'react-redux';
          import {addPersonAction} from "../../redux/action/personAction";
          
          function mapStateToProps(state) {
            return {};
          }
          
          
          class Manager extends Component {
            p = {}
          
            componentDidMount() {
            }
          
            addPerson = () => {
              const person = {
                name: this.p.name.value,
                size: this.p.size.value,
                more: this.p.more.value,
                id: new Date().getTime()
              }
              this.props.addPersonAction(person);
            }
          
            render() {
              return (
                <div>
                  名称:<input ref={c => this.p.name = c} type="text"/> <br/>
                  大小:<input ref={c => this.p.size = c} type="text"/> <br/>
                  备注:<input ref={c => this.p.more = c} type="text"/> <br/>
                  <br/>
                  <button onClick={this.addPerson}>添加</button>
                </div>
              );
            }
          }
          
          export default connect(
            mapStateToProps,
            {
              addPersonAction
            }
          )(Manager);
          
      • person

        • import React, {Component} from 'react';
          import {connect} from 'react-redux';
          import Manager from "./Manager";
          import Item from "./Item";
          
          function mapStateToProps(state) {
            return {
              arr: state.personReducer,
              count: state.countReducer
            };
          }
          
          
          class Person extends Component {
            componentDidMount() {
          
            }
          
            render() {
              const {arr, count} = this.props;
              return (
                <div>
                  <h1>Person</h1>
                  <p>count = {count}</p>
                  <Manager/>
                  {arr.map((it) => <Item key={it.id} {...it} />)}
                </div>
              );
            }
          }
          
          export default connect(
            mapStateToProps,
          )(Person);
          
  • app.js

    • import {Component} from "react";
      import {withRouter} from "react-router-dom";
      import './App.css';
      import {Provider} from "react-redux";
      import store from "./redux/store";
      import Person from "./component/person/Person";
      import Count from "./component/count/Count";
      
      
      class App extends Component {
      
        render() {
          return (
            <div>
              <Provider store={store}>
                <Count />
                <hr/>
                <Person/>
              </Provider>
            </div>
          );
        }
      }
      
      
      export default withRouter(App);
      

注意事项

如果在一个数组中删除某个元素,使用以下方式:

export const personReducer = (preState = [], action) => {
  const {data, type} = action;
  switch (type) {
    case ADD_PERSON:
      return [...preState, data];
    case REMOVE_PERSON:
      let index = 0;
      for (let i = 0; i < preState.length; i++) {
        if (preState[i].id === data) {
          index = i;
          break;
        }
      }
      preState.splice(index, 1)
      return preState;
    default:
      return preState;
  }
}

会出现页面无法更新的情况,但数组中的元素确实被删除了,但redux认为没有更新,因为会比较当前返回的元素和**之前的元素preState**的地址,如果两个地址一样就认为没有发生数据更新

纯函数

一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

必须遵守以下一些约束

  • 不得改写参数数据
  • 不会产生任何副作用,例如网络请求,输入和输出设备,也就是不靠谱的事不能做,因为网络请求可能会发生错误
  • 不能调用Date.now()或者Math.random()等不纯的方法

redux的reducer函数必须是一个纯函数

扩展

安装redux-devtools扩展

项目中:

npm install redux-devtools-extension

修改store

import {legacy_createStore as createStore, combineReducers} from "redux";
import {personReducer} from "./reducer/personReducer";
import {countReducer} from "./reducer/countReducer";
import {composeWithDevTools} from "redux-devtools-extension";

const allReducer = combineReducers({
  personReducer,
  countReducer
})
export default createStore(allReducer, composeWithDevTools())

懒加载

路由组件经常做懒加载,默认情况下页面加载完毕,所有的组件都会被引入,如果某个路由组件特别大,会造成页面首次加载时的白屏时间过长,可以使用懒加载

suspense中文为担心、焦虑,读音为səˈspens

import {Component, lazy, Suspense} from "react";
import {Link, Route, withRouter} from "react-router-dom";
import './App.css';

const Home = lazy(() => import('./component/Home'))
const About = lazy(() => import('./component/About'))


class App extends Component {
  state = {
    count: 0
  }

  plus = () => {
    this.setState((state, props) => {
      return {
        count: state.count + 1
      }
    }, () => {
      console.log(this.state)
    })
  }

  render() {
    const {count} = this.state;
    return (
      <div>
        <Link to={'/home'}>Home</Link>
        <br/>
        <Link to={'/about'}>about</Link>
        <Suspense fallback={<h1>加载中...</h1>}>
          <Route path={'/home'} component={Home} />
          <Route path={'/about'} component={About} />
        </Suspense>
      </div>
    );
  }
}


export default withRouter(App);
  • 路由组件的引入使用第5、6行的方式,第31行中指定了当组件加载时所显示的内容,可以放一个加载中的动画

Hooks

React 16.8新增的特性,可以在函数组件中使用一些类式组件才能够使用的特性

useState

使得函数组件使用state,按理说,页面更新时将会重新调用这个函数,当重新调用这个函数时,第4行将会被调用 会将num的值设置为0,但事实上并没有这个现象,这是因为底层做了处理,把num存了下来

import React, {useState} from 'react';

function Count(props) {
  const [num, setNum] = useState(0);
  return (
    <div>
      {num}
      <br/>
      <button onClick={() => setNum(num + 1)}>+1</button>
    </div>
  );
}

export default Count;

也可以传入一个函数,函数的参数为之前的值:

import React, {useState} from 'react';

function Count(props) {
  const [num, setNum] = useState(0);
  const plus = () => {
    setNum((pre) => {
      console.log(pre)
      return pre + 1;
    })
  }
  return (
    <div>
      {num}
      <br/>
      <button onClick={plus}>+1</button>
    </div>
  );
}

export default Count;

useEffect

effect中文为影响、效果、印象,读音为ɪˈfekt,可以在函数组件中使用生命周期

有两个参数,第一个参数为一个回调,第二个参数为数组:

  • 参数2数组中内容可以为通过useState创建的对象
  • 第二个参数代表检测数组中的对象值的变化,当某个对象变化了将会调用参数1的回调
  • 如果不提供数组,代表检测所有值的变化
  • 如果提供空数组,代表不检测任何值的变化,此时仅在组件加载时调用此回调

自动计数例子:

import React, {useEffect, useState} from 'react';

function Count(props) {
  const [num, setNum] = useState(0);
  useEffect(() => {
    const interval = setInterval(() => {
      setNum( num => num + 1);
    }, 1000);
    return () => {
      clearInterval(interval);
      console.log("此处相当于componentDidUnmounted")
    }
  }, []);


  const plus = () => {
    setNum(num + 1);
  }
  return (
    <div>
      {num}
      <br/>
      <button onClick={plus}>+1</button>
    </div>
  );
}

export default Count;

React 中,由于 setState 是异步执行的,所以直接修改 state 值可能会导致出现一些不可预测的行为。为了避免这种情况,React 提供了函数式更新的方式,即在 setNum 中传入一个函数,以确保更新 state 时使用最新的值。

因此,将代码中的 setNum(num + 1) 修改为 setNum(num => num + 1),这样每次调用 setNum 都会使用最新的 num 值进行更新。

可以看出,useEffect可以模拟componentDidMountedcomponentDidUpdatecomponentDidUnmounted三个生命周期的效果

useRef

import React, {useEffect, useRef, useState} from 'react';

function Count(props) {
  const content = useRef();
  const getContent = () => {
    console.log(content.current.value)
  }
  return (
    <div>
      <input ref={content} type="text"/>
      <br/>
      <button onClick={getContent}>获取内容</button>
    </div>
  );
}

export default Count;

fragment

React中不允许多个根标签,在之前的解决方案中,使用了<div></div>作为一个根标签,但在最后生成的HTML文件中包含了一级又一级的<div></div>,可以使用<Fragment></Fragment>代表一个根标签,这个根标签不会在最后的HTML文件中显示出来

也可以使用<></>空标签代替<Fragment></Fragment>,但空标签不能够写任何属性,<Fragment></Fragment>可以写一个key属性

PureComponent

在之前的React.Component中存在着效率低的问题,只要进行了setState,无论数据是否更新,都会调用render(),即使子组件没有用到父组件的数据,也会进行render()

使用PureComponent也会带来一些副作用,如果:

const obj = this.state.obj;
obj.aaa = 'aaaa';
this.setState(obj);

时,不会导致页面更新,因为底层仅仅进行了浅比较,原理是使用了shouldComponentUpdate()作了比较

useCallback

useCallback是一个React Hook,它可以让你缓存一个函数定义,避免在每次重新渲染时创建一个新的函数。要想使用useCallback,你需要做以下几步:

  • 在组件中调用useCallback函数,并传入两个参数:一个是你想要缓存的函数,另一个是一个依赖数组例如:
import React, { useCallback } from 'react'

function MyComponent() {
  // 缓存一个函数,接受一个name参数,并打印它
  const sayHello = useCallback(name => {
    console.log('Hello', name)
  }, [])
  // ...其他逻辑
}
  • useCallback函数会返回一个缓存的函数,你可以在组件中任何地方使用它
  • 例如:
import React, { useCallback } from 'react'

function MyComponent() {
  // 缓存一个函数,接受一个name参数,并打印它
  const sayHello = useCallback(name => {
    console.log('Hello', name)
  }, [])

  // 调用缓存的函数
  sayHello('React')
}
  • useCallback函数会根据依赖数组中的值来决定是否返回之前缓存的函数,还是返回当前传入的函数,如果依赖数组中的所有值都没有变化,useCallback会返回之前缓存的函数;如果依赖数组中的任何值有变化,useCallback会返回当前传入的函数,并把它缓存起来,以便下次使用。例如:
import React, { useState, useCallback } from 'react'

function MyComponent() {
  // 定义一个state变量count
  const [count, setCount] = useState(0)

  // 缓存一个函数,接受一个name参数,并打印它和count的值
  // 把count作为依赖数组中的唯一值
  const sayHello = useCallback(name => {
    console.log('Hello', name, count)
  }, [count])

  // 当count变化时,useCallback会返回当前传入的函数,并缓存它
  // 当count不变时,useCallback会返回之前缓存的函数

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <button onClick={() => sayHello('React')}>Say Hello</button>
    </div>
  )
}

useMemo

可以看作是一个计算属性

import {useMemo, useRef, useState} from "react";

function App() {
  const [user, setUser] = useState({
    firstName: '',
    lastName: ''
  });
  const fullName = useMemo(() => user.firstName + '-' + user.lastName, [user]);
  const getValue = (name) => {
    return (e) => {
      setUser(user => ({...user, [name]: e.target.value}))
    }
  }
  return (
    <>
      <input onChange={getValue('firstName')} type="text"/>
      <input onChange={getValue('lastName')} type="text"/>
      <br/>
      <p>{fullName}</p>
    </>
  );
}

export default App;

路由(新版)

<Switch></Switch>标签被移除掉了,<Route/>标签必须要放在<Routes/>下:

<Routes>
  <Route path={'/home'} element={<Home/>} />
  <Route path={'/about'} element={<About />} />
</Routes>

当某个路径没有被路由匹配到时,控制台会给出No routes matched location "路径" 的警告

可以用来跳转页面,可以理解为重定向

import React, {useState} from 'react';
import {Navigate} from "react-router-dom";

function Home(props) {
  const [sum, setSum] = useState(0);
  return (
    <>
      <p>home</p>
      <p onClick={() => setSum(a => a + 1)}>{sum}</p>
      <p>当num > 10时,将会跳转到about</p>
      {
        sum > 10 ? <Navigate to={'/about'} /> : <p>此时值小于10</p>
      }
    </>
  );
}

export default Home;

可以指定模式:<Navigate to={'/about'} replace />

useRoutes

也可以和navigate结合起来

import {Link, useRoutes} from "react-router-dom";
import Home from "./page/home";
import About from "./page/about";
import Person from "./page/person";

function App() {
  const element = useRoutes([
    {
      path: '/home',
      element: <Home/>
    },
    {
      path: '/about',
      element: <About/>
    },
    {
      path: '/person',
      element: <Person/>
    }
  ])
  return (
    <>
      <Link to={'/home'}>Home</Link>
      <br/>
      <Link to={'/about'}>About</Link>
      <div style={{
        width: 300,
        height: 200,
        border: '1px solid black'
      }}>
        {element}
      </div>
    </>
  );
}

export default App;

通常将路由文件抽取出来放到一个单独的文件中

Outlet

import Home from "../page/home";
import About from "../page/about";
import My from "../page/my";
import More from "../page/more";
import Person from "../page/person";

export default [
  {
    path: '/home',
    element: <Home/>
  },
  {
    path: '/about',
    element: <About/>,
    children: [
      {
        path: 'my',
        element: <My />
      },
      {
        path: 'more',
        element: <More />
      }
    ]
  },
  {
    path: '/person',
    element: <Person/>
  }
]

<Link to="路径"></Link>中,可以不用写全路径了,直接写路径代表从父路径匹配

import React from 'react';
import {Link, Outlet} from "react-router-dom";

function About(props) {
  return (
    <>
      <p>about</p>
      <Link to={'my'}>我的</Link> <Link to={'more'}>更多</Link>
      <div style={{
        width: 200,
        height: 100,
        border: '1px solid red',
        margin: 'auto'
      }}>
        <Outlet />
      </div>
    </>
  );
}

export default About;

useParams

{
  path: '/person',
  element: <Person/>,
  children: [
    {
      path: ':id/:title/:content',
      element: <Message />
    }
  ]
}

用法:

import React from 'react';
import {useParams} from "react-router-dom";

function Message(props) {
  const {id, title, content} = useParams();
  return (
    <>
      <ul>
        <li>{id}</li>
        <li>{title}</li>
        <li>{content}</li>
      </ul>
    </>
  );
}

export default Message;

useSearchParams

可以把query中的参数取出来

import React from 'react';
import {Link, Outlet} from "react-router-dom";

const arr = [
  {
    id: 1,
    title: '这是标题',
    content: '这是内容'
  },
  {
    id: 2,
    title: '这是标题222',
    content: '这是内容222'
  },
  {
    id: 3,
    title: '这是标题333',
    content: '这是内容333'
  },
]

function Person(props) {
  return (
    <>
      <p>person</p>
      {
        arr.map(it => <p key={it.id}><Link to={`message?id=${it.id}&title=${it.title}&content=${it.content}`}>{it.title}</Link></p>)
      }
      <Outlet />
    </>
  );
}

export default Person;
import React from 'react';
import {useSearchParams} from "react-router-dom";

function Message(props) {
  const [search, setSearch] = useSearchParams();
  const id = search.get('id');
  const title = search.get('title');
  const content = search.get('content');
  return (
    <>
      <ul>
        <li>{id}</li>
        <li>{title}</li>
        <li>{content}</li>
      </ul>
    </>
  );
}

export default Message;

useLocation

可以取出路径、传入的state

import React from 'react';
import {Link, Outlet} from "react-router-dom";
import message from "./message";

const arr = [
  {
    id: 1,
    title: '这是标题',
    content: '这是内容'
  },
  {
    id: 2,
    title: '这是标题222',
    content: '这是内容222'
  },
  {
    id: 3,
    title: '这是标题333',
    content: '这是内容333'
  },
]

function Person(props) {
  return (
    <>
      <p>person</p>
      {
        arr.map(it => <p key={it.id}><Link to={'message'} state={it}>{it.title}</Link></p>)
      }
      <Outlet />
    </>
  );
}

export default Person;
import React from 'react';
import {useLocation} from "react-router-dom";

function Message(props) {
  const {state:{id, title, content}} = useLocation();
  return (
    <>
      <ul>
        <li>{id}</li>
        <li>{title}</li>
        <li>{content}</li>
      </ul>
    </>
  );
}

export default Message;

useNavigate

可以实现编程式路由导航

import React from 'react';
import {Link, Outlet, useNavigate} from "react-router-dom";
import message from "./message";

const arr = [
  {
    id: 1,
    title: '这是标题',
    content: '这是内容'
  },
  {
    id: 2,
    title: '这是标题222',
    content: '这是内容222'
  },
  {
    id: 3,
    title: '这是标题333',
    content: '这是内容333'
  },
]

function Person(props) {
  const navigate = useNavigate();
  const look = (it) => {
    navigate('message', {
      replace: false,
      state: it
    })
  }
  return (
    <>
      <p>person</p>
      {
        arr.map(it => <p key={it.id}>
          <Link to={'message'} state={it}>{it.title}</Link>
          <button onClick={() => look(it)}>查看</button>
        </p>)
      }
      <Outlet />
    </>
  );
}

export default Person;

也可以用来实现前进和后退

React-Redux-Hooks

React-Redux Hooks React-Redux 提供的一组 API,可以在函数组件中订阅 Redux store 和派发 action。

React-Redux Hooks 包括以下三个 API:

  • useSelector():可以从 Redux store 中获取数据,使用一个选择器函数来指定你想要获取的数据。
  • useDispatch():可以获取一个 dispatch 函数,用来派发 action 到 Redux store。
  • useStore():可以获取 Redux store 的引用,用来访问 store 的状态或者替换 reducer。

使用 React-Redux Hooks 时,需要先用 <Provider> 组件包裹你的整个应用,以便让 store 在组件树中可用。

使用 React-Redux Hooks 有一些注意事项,比如:

  • useSelector() 默认使用 === 来比较结果,如果需要浅比较或者深比较,可以传入一个自定义的比较函数。
  • useSelector() 不会接收 ownProps 参数,如果需要使用 props,可以通过闭包或者柯里化的方式。
  • useDispatch() useStore() 只会返回同一个引用,不会在重渲染时改变。
  • 如果需要使用自定义的 context,可以使用 createDispatchHook()createSelectorHook() createStoreHook() 来创建对应的 Hooks

useSelector和useDispatch

import React from 'react';
import {useDispatch, useSelector} from "react-redux";
import {removePerson} from "../redux/action/personAction";

function MyFuncPerson(props) {
  const arr = useSelector(state => state.person);
  const dispatch = useDispatch();
  const remove = (data) => {
    return () => {
      dispatch(removePerson(data))
    }
  }
  return (
    <>
      <ul>
        {
          arr.map(it => <li key={it.id}>
            <span>{it.name}</span>
            <button onClick={remove(it.id)}>删除</button>
          </li>)
        }
      </ul>
    </>
  );
}

export default MyFuncPerson;
import React, {useRef} from 'react';
import {useDispatch} from "react-redux";
import {addPerson} from "../redux/action/personAction";

function MyFuncPersonAdd(props) {
  const name = useRef();
  const dispatch = useDispatch();
  const add = () => {
    dispatch(addPerson({
      id: new Date().getTime(),
      name: name.current.value
    }))
  }
  return (
    <>
      <input ref={name} type="text"/>
      <button onClick={add}>添加</button>
    </>
  );
}

export default MyFuncPersonAdd;

useStore

useStore是一个hook,它可以返回Redux store对象的引用,可以用来获取store的状态或替换reducer等操作,但不会订阅store的变化。useStore不会触发组件的重新渲染,也不会执行任何比较逻辑。useStore应该被少量使用,只有在一些不常见的场景下才需要用到,比如替换reduce

Q.E.D.


念念不忘,必有回响。