React相关

135 阅读10分钟

1. diffing算法和key

diffing算法步骤

  • react内部用虚拟DOM表示DOM树的结构, 虚拟DOM最后会被编译成真实DOM,插入到页面中
  • 当状态变更时,会重新构造一个虚拟DOM,新旧虚拟DOM会互相比较
  • 新虚拟DOM和旧虚拟DOM的差异构建到真实的DOM树上,就会驱动视图更新

新旧虚拟DOM比较的三种情况

  • 元素不同: 元素不同时, React会直接删除旧的DOM元素,不会复用
  • 元素相同,属性不同: React会重新设置属性
  • 元素和属性都相同: 只考虑位置变化

key值作用原理

  • key是唯一标识, 为了更高效率的更新虚拟DOM
  • 可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作的次数,提高性能。

2. JSX

JSX语法,是JavaScript语法的扩展,在React中配合JSX语法,JSX就是React元素

2.1 JSX语法注意事项

  • 在使用JSX时,标签必须要闭合
  • 使用JSX时,标签的名字首字母不允许大写 (大写字母会解析为组件)
  • 传入的JSX有且只能是一个根元素
  • 可以使用React提供的包裹组件,来解决一个根元素的问题。 React.Fragment只是起到了一个包裹的作用,不会转为真实dom
  • <></>也是一个包裹标签,是React.Fragment的语法糖。

2.2 插值表达式

jsx规定: 对jsx语法进行解析时, {} 里的内容会被解析为js表达式,并输出表达式的结果

  • js语法:
    • if 条件语句
    • for 循环语句
    • switch 选择语句
  • 表达式: 所谓的表达式就是一个表达式对应一个结果
    • 三元表达式
    • 变量
    • 常量
    • fn()

2.3 条件渲染

用react实现条件渲染

  • 方式1 : 三元表达式
  • 方式2 : && (与运算符)
  • 方式3 : || (或运算符)
// 三元表达式
<div>{sex===1?"男":"女"}|{bol?"真":"假"}</div>
// &&与运算符:该运算符左侧为true时,返回右侧的内容,如果为false,返回左侧内容。
 '天气不好' && "今天天气不错"
// ||或运算符:该运算符与&&运算符正好相反。左侧为false才会渲染右侧的值。
'林俊杰' || '周杰伦'

2.4 列表渲染

  • react中可以将数组中的元素依次渲染到页面上
  • 可以直接往数组中存储react元素
  • 推荐使用数组的 map 方法
  • 注意:必须给列表项添加唯一的 key 属性, 推荐使用id作为key, 尽量不要用index作为key
const arr = newsList.map((item,index)=>(
   <>
     <div>{index}</div>
     <div>{item.newsTitle}</div>
   </>
))
root.render(<div>{arr}</div>)

2.5 key

key 值是列表渲染时需要的唯一标识, 帮助react快速的找到真实DOM, 减少操作

2.6 绑定事件

jsx中命名事件需要驼峰命名(onClick) , 值必须是一个函数 例:

  const root = ReactDOM.createRoot(document.querySelector("#root"));
    function fn1(){
        console.log(this)
    }
    root.render((
        <div>
            <button onClick={fn1}>点我1</button>
        </div>
    ))

2. 生命周期

生命周期的三个阶段(新)

  • 初始化阶段: 由ReactDOM.render()触发---初次渲染
    • constructor()
    • getDerivedStateFromProps
    • render()
    • componentDidMount()
  • 更新阶段: 由组件内部this.setSate()或父组件重新render触发
    • getDerivedStateFromProps

    • shouldComponentUpdate()

    • render()

    • getSnapshotBeforeUpdate

    • componentDidUpdate()

  • 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
    • componentWillUnmount()

生命周期三个阶段 (旧)

  • 初始化阶段: 由ReactDOM.render()触发---初次渲染
    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount() 常用
      • 一般在这个钩子中做一些初始化的事情,
      • 比如开启定时器、发送网络请求、订阅消息
  • 更新阶段: 由组件内部this.setSate()或父组件重新render触发
    • shouldComponentUpdate()
    • componentWillUpdate()
    • render() 必须使用的一个
    • componentDidUpdate()
  • 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
    • componentWillUnmount() 常用
      • 一般在这个钩子中做一些收尾的事情,比如:关闭定时器、取消订阅消息

注意函数式组件没有生命周期, react用useEffect代替

useEffect可以接收一个回调函数和一个数组为参数,当不传第二个参数,该回调函数任意状态数据改变都会执行
一般在这个函数下发起网络请求

3. 函数式组件

  • React当中的组件分为:类组件(复杂组件,状态组件)与函数组件(简单组件,非状态组件)
  • 类组件:通过class定义的组件称为类组件
  • 函数组件:通过function定义的组件称为函数组件。

组件的作用:

  1. 可以多次使用
  2. 可以将重复的功能进行代码抽离,优化代码,提高阅读性

3.1 函数组件基本使用

function App(){
   return (
      <>
         {
            // <></>在map时,不可以作为包裹标签。因为无法增加属性key
            bookInfo.map(item=>(
               <React.Fragment key={item.id}>
                  <h3>{item.author}</h3>
                  <p>{item.bookName}</p>
               </React.Fragment>
            ))
          }
      </>
   )
}

4. 类组件

5. react组件通信

  • props

和vue类似,在父组件写props传递数据,在子组件通过形参接收,传递具体数据是父向子通信,传递函数可以子向父通信

  • 父组件
const state = {
    userName:"zhangsan",
    age:12
}
...
// 传递数据
<Child userName={state.userName} age={state.age}></Child>
...
  • 子组件
import PropTypes from "prop-types";
// 函数可以直接增加属性。称为函数的静态属性
Child.propTypes = {
    // userName属性必须是字符串,且不允许为空(undefined)
    userName:PropTypes.string.isRequired,
    // age属性必须是数字
    age:PropTypes.number
    // PropTypes.object 对象
    // PropTypes.array  数组
    // PropTypes.func   函数
}
// 可以设置默认值
Child.defaultProps = {
    userName:"zhaoliu"
}
...
// 接收数据
<p>userName:{props.userName}|{userName}</p>
<p>age:{props.age}|{age}</p>
...
  • useContext

通过React.createContext方法创建一个上下文对象,上下文对象有一个属性Provider 是一个组件,把接收共享数据的组件放在Provider组件中,在需要使用的地方用useContext接收

  • pubSub

发送消息(接收数据):PubSub.publish(名称,参数)
订阅消息(提供数据):PubSub.subscrib(名称,函数)

  • redux

多组件共享状态数据用redux

redux是一个组件通信方式,类似vue的vuex 主要有一个store和reducer,store中存储所有的共享状态数据,reducer中是数据修改的函数

可以通过store直接getstate得到数据,不能直接操作reducer,需要通过store去dispatch一个action,action就是操作数据的方法,有type和data

具体就是我们通过store dispatch一个action,reducer修改后这个数据返回给store,我们再通过store得到这个数据

store连接上reducer后会立即走一遍reducer,reducer中定义一个函数,两个参数,第一个是状态数据的值,第二个是action,那么可以通过在reducer中定义初始值,第一次连接就可以返回给store

在reducer中收到action,根据type操作数据并将其返回

当然对于异步操作,我们不能直接写在action里面,store不能接收异步的action,我们再action里面返回一个函数,这个函数内部进行dispatch,然后使用thunk中间件,通过applyMiddleware处理, 我们dispatch给中间件,中间件等异步操作有结果再传给store

redux-tookit是简化redux操作的语法糖,它通过createSlice方法传入配置项,将store和reducer结合起来,通过它可以得到所有的action和reducer react-redux是简化我们的使用,在app外面包裹一层provider,给内部容器提供store的值,我们在使用的时候通过useSelect得到state的值,通过usedispatch得到dispatch去提交action

6. React Hooks

Hooks 是一些特殊的函数,它可以让你“钩入” React 的特性。 hooks函数是以 use开头。 只能函数组件使用 原因: 函数组件没有state 没有生命周期 不用考虑this指向 方便维护

6.1 useState (常用)

一般的数据没有响应式效果,需要使用 useState()状态钩子

  • useState返回的是一个数组,接收的参数即是初始状态值。
  • 返回的数组有两个元素,第一个元素即是状态,第二个元素是修改状态的方法。
  • 当通过setXXX修改状态之后,函数会重新执行,注意:再次执行时,状态的值为上一次的结果(useState拥有记忆性)
  • 这个函数是异步函数
// 初始值为0,count为类似响应式数据
const [count, setCount] = useState(0) 
/* 不能直接修改,需要用setCount方法修改,且修改是直接整体替换,如果是对象类数据,需要先解构赋值,再覆盖.
并且修改是异步的,多次修改数据进行一次render渲染,类似vue中DOM更新也是遍历queue中所有watch的一个函数
 */

6.2 useCallback

  • 作用:记忆函数; useCallback:可以将函数进行缓存处理。
  • useCallback函数,接收的第一个参数是一个函数,第二个参数是数组
// 如果使用了useCallback,第二个参数建议声明,如果不声明相当于useCallback无用的方法。
// 如果省略第二个参数,不会有记忆。
// 如果只是写[],代表着该函数会进行记忆(缓存)
// 如果写的[num],代表着num发生变化,不会设置num缓存(保证num是最新的状态)。
function App(props) {
    const [num,setNum] = useState(1);
    const plus = useCallback(()=>{
        setNum(num+1);
    },[num])
    const subtract = useCallback(()=>{
        if(num>1){
            setNum(num-1)
        }
    },[num])
    return (
        <div>
            <button onClick={subtract}>-</button>
            {num}
            <button onClick={plus}>+</button>
        </div>
    );
}

6.3 useMemo

  • 作用: 记忆结果
// useCallback记忆的是函数,useMemo记忆的结果(将接收函数执行以后的结果进行返回)
// useMemo第二个参数是数组。
// 如果将第二个参数设置为空数组,那么所得到值不会发生变化。
// 如果将第二个参数省略,那么不会产生记忆。
// 如果将第二个参数设置为[num],只有状态num发生变化,才会重新调用指定的函数,得到新的结果。
const [num,setNum] = useState(1);
const plus = useCallback(()=>{
   setNum(num+1);
},[num])
const subtract = useCallback(()=>{
    if(num>1){
        setNum(num-1)
    }
},[num])
const result = useMemo(()=>{
   return (
      <div>
        <button onClick={subtract}>-</button>
           {num}
        <button onClick={plus}>+</button>
      </div>
    )
},[num])

6.4 useRef (常用)

  • 作用: 获取真实DOM
  • 将useRef函数运行的结果赋值给常量。
  • 将生成的常量作为操作的React元素的ref属性值
  • 在使用时,可以通过常量名.current可以得到真实的DOM。
const [num,setNum] = useState(1);
const btn = useRef();
const inputRef = useRef();
return (
  <div>
     <input ref={inputRef} type="text"/>
     <button ref={btn} onClick={()=>{
        console.log(inputRef.current.value)
     }}>{num}</button>
  </div>
)

6.5 useContext (常用)

  • 作用: useContext可以帮助我们解决跨组件传递数据,实现数据的共享。

4. 关于路由

  • 使用useRoutes根据配置的路由表注册路由
  • 路由跳转:编程式导航和声明式导航和vue类似
  • 可以写navlink标签,to属性跳转
  • 也可以使用useNavigate(),然后调用对应函数,进行跳转

三种传参

  • params:和vue-router类似,路径后直接带参数,注册路由后冒号占位,使用时用useparams接收
  • query:路径后用问号拼接,使用时用uselocation接收
  • state:编程式导航可以携带state参数,同样用uselocation接收

5. 受控表单

主要是看是否受用户控制

受控表单

在input框上输入页面同时显示改变的内容,主要因为我们绑定了表单的change事件,触发时改变状态数据

非受控表单

我们不能直接控制,只能通过ref获取 函数式组件通过useRef创建容器,获取写有ref的真实dom

高阶组件

vue中可以通过路由守卫对路由跳转进行操作,那么就可以在守卫里进行判断登录,权限校验的操作 react没有路由守卫,通过高阶组件方式进行权限控制的操作,其类似一种中间件,定义一个组件,所有设置的路由组件都会经过他并原样返回,而这个高阶组件会传入一个函数中进行各种判断达到权限控制的效果

react事件

react事件不是原生事件, 是对原生事件包装的合成事件(内部包含了原生事件对象的引用), 目的屏蔽浏览器事件处理的差异 react绑定事件只能写回调函数,事件绑定给了document,用事件委托监视事件触发

<input onChange={() => {}} /> react中的change在input上是在输入过程中实时触发 因为内部包装的是原生的input事件