xccjk / x-blog

学习笔记
17 stars 2 forks source link

【React Hooks 核心原理与实战】 #83

Closed xccjk closed 1 year ago

xccjk commented 1 year ago

01-如何创建React应用

传统开发UI的方式:

基于浏览器DOM的API,来控制DOM节点的创建、修改、删除。为了保证UI的一致性,需要非常小心处理各种数据导致的UI变化

React开发UI的方式:

当数据发生变化时,UI能够自动把变化反应出来。用声明式的方式来描述数据和UI之间的关系

React的基本概念

React的三个基本概念:组件、状态、jsx

组件

组件分为内置组件和自定义组件,自定义组件必须以大写开头。React组件是以树结构组织在一起的,一个React应用通常只有一个根组件

一个评论组件的组织方式:

const CommentBox = () => {
  return (
    <div>
        <CommentHeader />
        <CommentList />
        <CommentForm />
    </div>
  )
}

export defaule CommentBox

state 和 props来进行状态管理

state是用来在组件内部保存状态的

一个计数器组件:

import React, { useState } from 'react'

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <div onClick={() => setCount(count + 1)}>
      {count}
    </div>
  )
}

export default Counter

props是用来在父子组件自检传递状态

一个用户信息展示组件:

import React, { useState } from 'react'

const Avatar = ({ src }) => {
  return <img src={src} />
}

const User = () => {
  const [user, setUser] = useState({ name: 'xcc', avatar: 'https://xxx.png' })
  return (
    <div>
        <Avatar {...user} />
    <div>{user.name}</div>
    </div>
  )
}

export defaule User

jsx本质

jsx并不是一种新的模板语言,而可以认为是一种语法糖。React也可以不使用jsx语法来实现

计数器组件的非jsx语法实现:

React.createElement( 
  "div", 
  { 
    onClick: function onClick() { 
      return setCount(count + 1)
    } 
  }, 
  null
)

通过React.createElement API创建一个组件的实例,这个API会接收一组参数:

常见创建React应用的方式

一个React应用,应该包含路由管理、状态管理、JavaScript新语言特性的支持、CSS预处理框架等

使用create-react-app创建应用

npx create-react-app app
cd app
npm start
xccjk commented 1 year ago

React为什么要发明Hooks

16.8版本推出Hooks后,对以前的Class组件完全没影响。原来的Class组件完全可以继续使用,这两种开发方式是并存的,已有代码不需要任何改动,新的逻辑可以看具体情况来采用Hooks方法来实现

相比Vue的版本升级,React的每次版本升级,都是向后兼容的,每次版本升级也非常的丝滑

React组件的本质

所有的UI都是由状态来驱动的。状态发生变化时,UI会自动变化,这就是数据绑定

Class组件与Hooks组件对比

React Hooks解决的问题

React Hooks的优势

逻辑复用能力强

react class render props实现获取鼠标在页面的位置的逻辑复用

mouse-position.js

import React, { Component } from 'react'

class MousePosition extends Component {
  constructor(props) {
    super(props)
    this.state = {
      x: 0,
      y: 0
    }
  }

  handleMouseMove = (e) => {
    const { clientX, clientY } = e
    this.setState({
      x: clientX,
      y: clientY
    })
  }

  componentDidMount() {
    document.addEventListener('mousemove', this.handleMouseMove)
  }

  componentWillUnmount() {
    document.removeEventListener('mousemove', this.handleMouseMove)
  }

  render() {
    const { children } = this.props
    const { x, y } = this.state
    return(
      <div>
        {
          children({x, y})
        }
      </div>
    )
  }
}

app.js

import React from "react";
import MousePosition from "./mouse-position";

export default function App() {
  return (
    <div className="App">
      <MousePosition>
        {({ x, y }) => {
          return (
            <div>
              <p>
                x:{x}, y: {y}
              </p>
            </div>
          );
        }}
      </MousePosition>
    </div>
  );
}

react class hoc实现获取鼠标在页面的位置的逻辑复用

mouse-position.js

import React from "react";

const MousePosition = (Component) => {
  class MousePositionComponent extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        x: 0,
        y: 0
      };
    }

    handleMouseMove = (e) => {
      const { clientX, clientY } = e;
      this.setState({
        x: clientX,
        y: clientY
      });
    };

    componentDidMount() {
      document.addEventListener("mousemove", this.handleMouseMove);
    }

    componentWillUnmount() {
      document.removeEventListener("mousemove", this.handleMouseMove);
    }

    render() {
      const { x, y } = this.state;
      return <Component x={x} y={y} />;
    }
  }
  return MousePositionComponent;
};

export default MousePosition;

app.js

import React from "react";
import MousePosition from "./mouse-position";

function App({ x, y }) {
  return (
    <div>
      <p>
        x:{x}, y: {y}
      </p>
    </div>
  );
}

export default MousePosition(App);

react hooks实现获取鼠标在页面的位置的逻辑复用

use-position.js

import { useState, useEffect } from "react";

const usePosition = () => {
  const [position, setPosition] = useState({
    x: 0,
    y: 0
  });

  const handleMouseMove = (e) => {
    const { clientX, clientY } = e;
    setPosition({ x: clientX, y: clientY });
  };

  useEffect(() => {
    window.addEventListener("mousemove", handleMouseMove);
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, []);

  return position;
};

export default usePosition;

app.js

import React from "react";
import usePosition from "./use-position";

export default function App() {
  const position = usePosition();
  return (
    <div>
      <p>
        x:{position.x},y:{position.y}
      </p>
    </div>
  );
}

代码更加精简

class实现计时器组件:

import React from 'react'

class Counter extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
  }
  handleClick = () => {
    let { count } = this.state
    this.setState({
      count: count+1
    })
  }
  render() {
    const { count } = this.state
    return (
      <div>
        <p>{ count }</p>
        <button onClick={this.handleClick}>点击</button>
      </div>
    )
  }
}

export defaule Counter

hooks实现计时器组件:

import React, { useState } from 'react'

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{ count }</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  )
}

export default Counter

业务代码更加聚合

class组件中监听页面缩放:

import React from 'react'

class WindowSize extends Component {
  constructor(props) {
    super(props)
    this.state = {
      size: null
    }
  }

  componentDidMount() { 
    window.addEventListener("resize", this.handleResize)
  }

  componentWillUnmount() { 
    window.removeEventListener("resize", this.handleResize)
  }

  handleResize = () => {
    ...
  }

  render() {
    ...
  }
}

export default WindowSize

hooks组件监听页面缩放:

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

const WindowSize = () => {
  const [size, setSize] = useState(null)

  useEffect(() => {
    const handleResize = () => {
      ...
    }
    window.addEventListener("resize", handleResize)
    return () => {
      window.removeEventListener("resize", handleResize)
    }
  }, [])

  return (
    ...
  )
}

export default WindowSize
xccjk commented 1 year ago

几种常见的React Hooks

useState

useState,让函数组件拥有了维持状态的功能。维持状态是指在函数的多次渲染之间,这个state是可以在函数中间共享的

使用方式

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{ count }</p>
      <button onClick={() => setCount(count + 1)}>点击</button>
    </div>
  )
}

export default Counter

参数解析

useState(initialState)的参数initialState是state的初始值,可以是任意类型,如字符串、数值、布尔值、对象、数组

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  const [visible, setVisible] = useState(false);
  const [store, setStore] = useState({});
  const [list, setList] = useState([]);
  const [name, setName] = useState('xcc');

  return ...
}

export default Counter

useState()返回值是一个包含两个元素的数组,第一个值为state的值,为只读的,第二个是用来设置state值的

对比类组件

类组件只有一个state,通过通过对象中的不同属性来表示不同的状态,通过setState来进行更新。函数组件可以创建多个state,用来描述不同状态的值,更加语义化

类组件:

import React from 'react'

class Counter extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0,
      visible: false
    }
  }

  componentDidMount() { 
    setState({ count: 1 })
  }

  render() {
    ...
  }
}

export default Counter

函数组件:

import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  const [visible, setVisible] = useState(false);

  return ...
}

export default Counter

使用时需要注意的点

不要在state中存储可以通过计算得到的值

常见情况:

  1. props中传递过来的值
  2. 存储在cookie、localStorage中的值
  3. 挂载在URL参数上的值

useEffect

useEffect,用来执行一段副作用,副作用是指一段和当前执行结果无关的代码。其中代码的执行是不会影响UI的渲染

使用方式

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

const WindowSize = () => {
  const [size, setSize] = useState(null)

  useEffect(() => {
    const handleResize = () => {
      ...
    }
    window.addEventListener("resize", handleResize)
    return () => {
      window.removeEventListener("resize", handleResize)
    }
  }, [])

  return (
    ...
  )
}

export default WindowSize

参数解析

useEffect(callback, dependencies)

callback为要执行的函数,dependencies为可选的依赖项数组

几种常见的使用方式:

没有依赖项时,会在每次render后执行

useEffect(() => {})

依赖项为空数组时,会在首次render后执行

useEffect(() => {}, [])

依赖项不为空时,会在首次render后,每次依赖项数组发生改变时执行

useEffect(() => {}, [a,b,c])

组件在卸载时执行

useEffect(() => { return () => {} }, [])

对比类组件

useEffect想比类组件的生命周期,基本可以等价于ComponentDidMount、componentDidUpdate 和 componentWillUnmount这三个方法,但有不是完全相同

类组件

import React from 'react'

class WindowSize extends Component {
  constructor(props) {
    super(props)
    this.state = {
      size: null
    }
  }

  componentDidMount() { 
    window.addEventListener("resize", this.handleResize)
  }

  componentWillUnmount() { 
    window.removeEventListener("resize", this.handleResize)
  }

  handleResize = () => {
    ...
  }

  render() {
    ...
  }
}

export default WindowSize

函数组件:

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

const WindowSize = () => {
  const [size, setSize] = useState(null)

  useEffect(() => {
    const handleResize = () => {
      ...
    }
    // 等价于类组件中的componentDidMount
    window.addEventListener("resize", handleResize)
    // 等价于类组件中的componentWillUnmount
    return () => {
      window.removeEventListener("resize", handleResize)
    }
  }, [])

  return (
    ...
  )
}

export default WindowSize

使用时注意的点

依赖项一定要为回调函数中在使用的变量

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

const Books = ({ id }) => {
  const [list, setList] = useState([])

  useEffect(() => {
    const getList = async () => {
      const res = await getBookList({ id })
      setList(res.data)
    }
    getList()
  }, [id])

  return ...
}

export defaule Books;

依赖项要为一个常量数组,而不是一个动态数据

useEffect(() => {}, [id])

React会使用浅比较来对比依赖项是否发生改变,因此不要使用对象、数组等作为依赖项

function Todos() {
  // 这里在每次组件执行时创建了一个新数组
  const todos = [{ text: 'Learn hooks.'}];
  useEffect(() => {
    console.log('Todos changed.');
  }, [todos]);
}
xccjk commented 1 year ago

函数组件中useState的解决方案

xccjk commented 1 year ago

React Hooks的使用规则

xccjk commented 1 year ago

React中state使用注意的点

保证状态最小化

避免中间状态,确保唯一数据源