likes
comments
collection
share

React学习笔记(二)

作者站长头像
站长
· 阅读数 25

前言

书接上文,React继续学习并记录笔记。本文有好多是问chatGPT得到的,chatGPT回答结合着例子,感觉更加通俗易懂些。

学习步骤

  • react hooks
  • 组件间的传值
  • 组件内常用的使用方式(类比vue,v-if,v-for试试例子)

react hooks

什么是react hooks

我去问了问chatGPT,回答如下: React学习笔记(二)

react hooks解决了什么问题

React学习笔记(二) 大概就是这三点,网上的回答也大同小异:

  • 让函数组件也能做类组件的事,有自己的状态,可以处理一些副作用,能获取 ref ,也能做数据缓存。
  • 解决逻辑复用难的问题
  • 放弃面向对象编程,拥抱函数式编程

什么是自定义hooks

React学习笔记(二)

常用的React hooks

useState

useState 可以使函数组件像类组件一样拥有 state,函数组件通过 useState 可以让组件重新渲染,更新视图。

const [ ①state , ②dispatchAction ] = useState(③initData)

① state,目的提供给 UI ,作为渲染视图的数据源。

② dispatchAction 改变 state 的函数,可以理解为推动函数组件渲染的渲染函数。

③ initData 有两种情况,第一种情况是非函数,将作为 state 初始化的值。 第二种情况是函数,函数的返回值作为 useState 初始化的值。

import { useState } from "react"

interface myInterface{
    name:string;
}

const DemoState = (props:myInterface) => {
    console.log(props)
    /* number为此时state读取值 ,setNumber为派发更新的函数 */
    let [number, setNumber] = useState(0) /* 0为初始值 */
    return (<div>
        {/* 这里展示的又是最新的值,因为在整个事件处理结束之后再重新渲染组件,此时state已经更新好的 */}
        <span>{ number }</span>  
        <button onClick={ ()=> {
        setNumber(number+1)
      console.log(number) /* 由于 useState 是异步的,点击时state还没有更新好,所以 console.log 同步输出的是上一次更新后的值,并不是最新的值。  */
    } } ></button>
    </div>)
}

export default DemoState

useRef

useRef 可以用来获取元素,缓存状态,接受一个状态 initState 作为初始值,返回一个 ref 对象, 这个对象上有一个 current 属性就是 ref 对象需要获取的内容。

const ref = React.useRef(initState)
console.log(ref.current)

useRef常用用法

  • useRef 获取 DOM 元素,在 React Native 中虽然没有 DOM 元素,但是也能够获取组件的节点信息,如下:
const DemoUseRef = ()=>{
  const dom= useRef(null)
  const handerSubmit = ()=>{
    /*  <div >表单组件</div>  dom 节点 */
    console.log(dom.current)
  }
  return <div>
    {/* ref 标记当前dom节点 */}
    <div ref={dom} >表单组件</div>
    <button onClick={()=>handerSubmit()} >提交</button> 
  </div>
}

  • 可以利用 useRef 返回的 ref 对象来保存状态,只要当前组件不被销毁,那么状态就会一直存在。具体如下:
  1. 定义一个ref变量
const myRef = useRef(null);
  1. 使用myRef保存状态
const [myState, setMyState] = useState(0);
useEffect(() => {
  myRef.current = myState;
}, [myState]);

上面的代码中,我们定义了一个名为myRef的ref变量,并在useEffect钩子函数中将myState保存到myRef中。这里要注意的是,因为myRef.current是一个可变的变量,所以当myState变化时,我们需要使用数组的解构赋值和useState钩子来更新myState的值。

  1. 读取保存的状态
console.log(myRef.current);

在组件的任何部分,我们都可以使用myRef.current来读取已经保存的状态。

最后需要说明的是,useRef保存的状态在每次重新渲染时都会持续存在,这意味着它可以在不同的渲染之间长期存储数据,而不像useState只会在当前的渲染周期中有效。所以如果需要保存状态并且不想在rerender时失去这个状态,可以用useRef钩子。

useEffect

React的useEffect钩子用于在React组件渲染完成后执行一些副作用操作,比如访问API、更新DOM等。它的作用类似于类组件中的生命周期函数。 useEffect具有三种执行方式:

初始化渲染

初始渲染在初始渲染时,useEffect会在组件挂载之后立即执行回调函数。如果指定了依赖项,React会检查每个依赖项是否发生变化,如果有,则重新执行回调函数。

更新渲染

在组件更新时,useEffect会在所有更新完毕后执行回调函数。如果指定了依赖项,React会检查每个依赖项是否发生变化,如果有,则重新执行回调函数。

卸载组件

在组件卸载时,useEffect会执行清除副作用操作的回调函数。这种情况下不需要指定依赖项。

useEffect钩子接收两个参数,第一个参数是一个函数,这个函数包含需要执行的副作用操作,例如在钩子中访问API或者更新DOM。第二个参数是一个数组,它用来指定useEffect什么情况下需要重新运行。如果不需要重新运行,可以将数组参数留空。

下面是一些useEffect的用例:

  1. 访问API并更新状态
import React, { useState, useEffect } from "react";

function App() {
  const [data, setData] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const result = await axios.get("https://api.example.com/data");
      setData(result.data);
    }
    fetchData();
  }, []);

  return (
    <div>
      {data.map((item) => (
        <p key={item.id}>{item.name}</p>
      ))}
    </div>
  );
}

export default App;

上面的代码中,我们在useEffect钩子中发起了一个axios的get请求,并将获取到的数据设置到data状态中。需要注意的是,由于我们在useEffect钩子函数中调用了async函数,所以我们需要在函数前面加上async关键字。

  1. 监听props的变化并更新状态
import React, { useState, useEffect } from "react";

function App(props) {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    setCounter(props.value);
  }, [props.value]);

  const handleClick = () => setCounter(counter + 1);

  return (
    <div>
      <p>{counter}</p>
      <button onClick={handleClick}>Increase</button>
    </div>
  );
}

export default App;

上面的代码中,我们使用了useEffect钩子和useState钩子,以响应props的变化并更新状态。钩子中的函数会在props.value改变时触发,它将props.value的值设置到counter变量中。

总的来说,useEffect是React中非常重要的一个钩子,它可以让我们更加容易地进行组件的副作用操作,并且可以防止我们在组件渲染完成前执行操作。

useMemo

React的useMemo钩子用于缓存函数的计算结果,以减少不必要的计算和渲染。它的作用范围更广,可以缓存任何复杂的计算结果。

useMemo接收两个参数,第一个参数是一个函数,这个函数包含需要执行的计算操作,例如根据props和状态计算出一个值或者过滤一些数据。第二个参数是一个数组,它用来指定这个函数依赖的数据,只有当依赖数据发生变化时才会重新计算缓存结果。如果依赖数据没有变化,则直接返回上一次计算的缓存结果。

下面是一些useMemo的用例:

  1. 根据props和状态计算出一个值
import React, { useMemo, useState } from "react";

function App(props) {
  const [count, setCount] = useState(0);

  const result = useMemo(() => {
    console.log("Compute Result");
    return count * props.value;
  }, [count, props.value]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Value: {props.value}</p>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
    </div>
  );
}

export default App;

上面的代码中,我们使用了useMemo钩子来缓存计算结果,这个计算结果基于state变量count和props中的值计算而来。由于这个计算结果只依赖于count和props.value,所以只有count或props.value变化时,useMemo才会重新计算这个计算结果。同时,由于这个计算结果被缓存起来了,所以即使我们反复点击Increase Count按钮,计算结果也不会重新计算和渲染。

  1. 从数组中过滤出存在某个字符的项
import React, { useState, useMemo } from "react";

function App() {
  const [list, setList] = useState(["apple", "banana", "mango", "peach"]);
  const [search, setSearch] = useState("");

  const filteredList = useMemo(() => {
    console.log("Filter List");
    return list.filter((item) => item.includes(search));
  }, [list, search]);

  const handleChange = (event) => {
    setSearch(event.target.value);
  };

  return (
    <div>
      <input value={search} onChange={handleChange} />
      <ul>
        {filteredList.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

上面的代码中,我们使用了useMemo钩子来缓存计算结果,这个计算结果被过滤离数组list中包含搜索关键词的项。由于这个计算结果只依赖于list和search变量,所以只有这两个变量发生变化时,useMemo才会重新计算。同样,由于这个过滤结果被缓存起来了,即使我们在搜索框中不断输入文字或者反复添加删除项,useMemo也只会在依赖变化时重新计算。

useLayoutEffect

React的useLayoutEffect钩子与useEffect类似,都是用于执行副作用操作,但是useLayoutEffect会在浏览器绘制之前执行,可以保证副作用操作对页面布局的影响被计算在内。

useLayoutEffect的使用方法和useEffect一样,都是接收一个函数和一个依赖数组。useLayoutEffect钩子的使用场景与useEffect相似,当需要在组件挂载或更新后立即执行一个副作用操作,以确保副作用操作对页面布局有影响时,可以使用useLayoutEffect。

下面是一个useLayoutEffect的用例:

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

function App() {
  const [width, setWidth] = useState(0);
  const ref = useRef();

  useLayoutEffect(() => {
    setWidth(ref.current.offsetWidth);
  }, []);

  return (
    <div>
      <h1 ref={ref}>Hello, World!</h1>
      <p>The width of the h1 element is {width}px.</p>
    </div>
  );
}

export default App;

上面的代码中,我们使用了useLayoutEffect钩子来获取h1元素的宽度,并将它保存到width状态变量中。由于useLayoutEffect钩子在DOM更新之前执行,在这个例子中,我们可以保证获取的宽度值是最新的,同时,由于我们使用useLayoutEffect钩子来获取宽度值,所以这个宽度值的更新不会导致额外的页面渲染。

useContext

React的useContext钩子用于获取React上下文的值。上下文可以理解为一个全局对象,其中保存了在组件树中被共享的数据。使用useContext可以使得在组件树的任何一个组件中都能够访问到上下文的值。

useContext接收一个上下文对象作为参数,并返回这个上下文对象的当前值。上下文对象在创建时由React.createContext函数创建。

下面是一个useContext的用例:

import { createContext, useContext } from "react";

const ThemeContext = createContext("light");

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Parent />
    </ThemeContext.Provider>
  );
}

function Parent() {
  return <Child />;
}

function Child() {
  const theme = useContext(ThemeContext);

  return <div>Current theme is {theme}</div>;
}

export default App;

上面的代码中,我们创建了一个名为ThemeContext的上下文,它的默认值为"light"。在App组件中,我们使用ThemeContext.Provider提供了一个新的值"dark",并将这个值传递给了Child组件。在Child组件中,我们使用了useContext钩子来获取当前的主题值,并将这个值显示在页面上。

需要注意的是,由于useContext只能在函数组件中使用,所以需要将上下文对象和Provider组件都放在函数组件外面或者使用React.memo包裹组件。另外,需要通过Context.Provider提供一个新值,以便在组件层次中下传下去。

useReducer

React的useReducer钩子用于管理和更新应用程序的状态,可与Redux架构相比。与useState(类似)不同之处在于,useState钩子通常用于管理状态值。useReducer则被视为管理复杂状态和操作的更好解决方案。

useReducer接收两个参数:一个是reducer函数,一个是初始状态值。reducer函数的作用是在更新状态之前处理一个操作(或动作),并且需要返回一个新的状态值。所得到的状态值将被作为useReducer钩子的返回值,同时,还将被传递到应用程序中的其他组件中。

下面是一个useReducer的用例:

  import { useReducer } from "react";

    const initialState = {
        count: 0
    };

    function reducer(state, action) {
        switch (action.type) {
            case "increment":
                return { count: state.count + 1 };
            case "reset":
                return { count: initialState.count };
            default:
                throw new Error("Invalid action type");
        }
    }

    function App() {
        const [state, dispatch] = useReducer(reducer, initialState);

        return (
            <div>
                <p>Count: {state.count}</p>
                <button onClick={() => dispatch({ type: "increment" })}>Click to increment count: {state.count}</button>
                <button onClick={() => dispatch({ type: "reset" })}>Reset count</button>
            </div>
        );
    }

    export default App;

上面的代码中,我们使用了useReducer钩子来创建一个与状态相关的reduce函数。该reduce函数管理一个名为“count”的状态计数器。在组件中,我们显示此计数器状态的当前值,并将两个按钮连接到dispatch方法。第一个按钮将用于increment操作,第二个用于reset计数器。

需要注意的是,在dispatch方法中,我们使用action对象来通知reducer函数要执行什么样的操作。这个对象是一个简单的JavaScript对象,包含一个字符串类型的属性,表示操作类型(在本例中,我们使用“increment”和“reset”作为操作类型字符串)。

总之,useReducer是一种更好的状态管理解决方案,可以轻松地管理复杂的组件状态。

useImperativeHandle

useImperativeHandle 可以配合 forwardRef 自定义暴露给父组件的实例值。使得父组件能够通过ref获取到子组件的实例,并且调用这些自定义的API。通常用于实现一些跨组件的通讯,或者是对某些组件逻辑的封装,可以将它看成是将一些子组件的方法暴露给父组件来调用。

useImperativeHandle接收两个参数:一个Ref(引用)对象和一个返回一个对象的函数。返回的对象表示要暴露给父组件的API接口。父组件可以通过ref来调用这些自定义的方法。

下面是一个useImperativeHandle的用例:

import { forwardRef, useImperativeHandle, useState } from "react";

const Child = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);

  useImperativeHandle(ref, () => ({
    increment() {
      setCount(count + 1);
    },
    reset() {
      setCount(0);
    },
  }));

  return <div>Count: {count}</div>;
});

function App() {
  const childRef = useRef();

  function handleIncrement() {
    childRef.current.increment();
  }

  function handleReset() {
    childRef.current.reset();
  }

  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleReset}>Reset</button>
    </div>
  );
}

export default App;

上面的代码中,我们创建了一个名为Child的组件,它包含了一个名为“count”的状态计数器,并将它通过useImperativeHandle钩子暴露给了父组件。然后,我们使用forwardRef钩子将Child组件的ref引用传递给了App组件。在App组件中,我们使用ref来调用Child组件中的increment和reset方法。通过useImperativeHandle,我们可以在父组件中调用Child组件的自定义方法,实现了跨组件的通讯。

需要注意的是,如果你正在使用useImperativeHandle来暴露方法,则必须将子组件包装在forwardRef中,以便可以在父组件中使用ref引用。

实现一个自定义的React的自定义Hooks

可以采用以下步骤来创建一个自定义Hooks:

  1. 创建一个以"use"开头的函数,这是React在命名约定中使用的方法,以通知其他程序员此函数是一个Hook;
  2. 在函数中编写一些逻辑,可以使用现有的Hooks,如useState、useEffect;
  3. 根据需要返回一些有用的值。

以下是一个简单的示例,它使用useState和useEffect自定义了一个计时器hooks:

import { useState, useEffect } from "react";

function useTimer(initialTime) {
  const [time, setTime] = useState(initialTime);

  useEffect(() => {
    const timer = setInterval(() => {
      setTime(time => time + 1);
    }, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  return time;
}

export default useTimer;

上面的代码中,我们定义了一个名为useTimer的自定义Hooks,并使用useState和useEffect在内部实现了一个计时器的逻辑,以输出时间。这个自定义Hooks可以用于任何需要计时器的组件中,并使得代码的重复度更低。

使用自定义Hooks非常简单,只需要导入并使用即可。例如:

import useTimer from "./useTimer";

function App() {
  const time = useTimer(0);

  return (
    <div>
      <p>Current Time: {time} seconds</p>
    </div>
  );
}

export default App;

在上面这个例子中,我们导入了自定义Hooks useTimer,并在App组件中调用了它。最终,我们成功地使用自定义Hooks来达到了逻辑复用的效果。

总之,自定义Hooks是非常有用的,可以让我们在函数组件之间共享逻辑,并且使得我们的代码更加简洁易懂。当多个组件需要相同的逻辑时,它可以提高代码的重用性和可维护性。

组件间的传值

父子组件传值

父传子

在React中,父组件可以向子组件传递数据(props)以及函数作为回调来处理事件。在函数式组件中,可以通过参数的形式来接收父组件传递的props。

例如,父组件可以这样传递数据和函数:

  function ParentComponent() {
      const data = { name: 'John', age: 30 };
      const handleClick = () => console.log('Button clicked');

      return (
        <ChildComponent data={data} onClick={handleClick} />
      );
    }

而在子组件中,可以使用props来接收这些参数:

 function ChildComponent(props) {
      const { data, onClick } = props;

      return (
        <div>
          <p>Name: {data.name}</p>
          <p>Age: {data.age}</p>
          <button onClick={onClick}>Click me</button>
        </div>
      );
    }

在子组件中使用props.xxx即可获取父组件传递的数据或函数。这样,在子组件中就可以使用这些数据和函数来展示内容或处理事件了。

子传父

在React中,在函数式组件中子组件向父组件传递数据的方式是通过回调函数,父组件将一个函数作为props传递给子组件,然后子组件在需要向父组件传递数据时,调用该函数并传递参数即可。具体实现方法如下:

父组件:

import React, { useState } from 'react';
import Child from './Child';

function Parent() {
  const [message, setMessage] = useState('');

  function handleMessage(message) {
    setMessage(message);
  }

  return (
    <div>
      <h2>Parent Component</h2>
      <Child onMessage={handleMessage} />
      <p>Message from child: {message}</p>
    </div>
  );
}

export default Parent;

子组件:

import React, { useState } from 'react';

function Child(props) {
  const [inputValue, setInputValue] = useState('');

  function handleInput(event) {
    setInputValue(event.target.value);
  }

  function sendMessage() {
    props.onMessage(inputValue);
  }

  return (
    <div>
      <h3>Child Component</h3>
      <input type="text" value={inputValue} onChange={handleInput} />
      <button onClick={sendMessage}>Send message to parent</button>
    </div>
  );
}

export default Child;

在上述代码中,父组件通过onMessage属性向子组件传递一个函数handleMessage,子组件触发sendMessage函数来传递参数inputValue给父组件。子组件通过调用props.onMessage(inputValue)来触发父组件中的回调函数handleMessage,实现了子组件向父组件传递数据的功能。

兄弟组件传值

在 React 中,兄弟组件之间的传值通常需要借助父组件作为中间层,通过向父组件传递数据再由父组件传递给另一个兄弟组件实现。同时,我们可以使用 context APIRedux 等方案进行数据管理。

总体思路:将状态共享,提升到最近的公共父组件中,由父组件管理状态

  • 提升公共状态
  • 提供操作共享状态的方法
import React, { useState } from 'react';

function Parent() {
  const [message, setMessage] = useState('Hello, World!');

  function handleMessageChange(message) {
    setMessage(message);
  }

  return (
    <div>
      <Child1 onMessageChange={handleMessageChange} />
      <Child2 message={message} />
    </div>
  );
}

function Child1(props) {
  function handleChange(e) {
    props.onMessageChange(e.target.value);
  }

  return (
    <div>
      <input type="text" onChange={handleChange} />
    </div>
  );
}

function Child2(props) {
  return (
    <div>
      <p>{props.message}</p>
    </div>
  );
}

我们定义了一个名为 Parent 的组件作为中间层,分别传递给了 Child1 和 Child2 两个组件。Child1 的输入框可以让用户输入任意文本,并将文本内容通过 props.onMessageChange 传递给父组件 Parent 中的 handleMessageChange 方法进行处理。父组件 Parent 的 handleMessageChange 方法通过设置 message 的状态来记录下输入框中的文本。最后,将 message 状态通过 props 传递给 Child2 组件进行渲染。

需要注意的是,在实际开发中,如果组件之间关系比较紧密,或者组件之间需要频繁地传递数据,则建议考虑使用 context APIRedux 等方案进行数据管理,这样可以使代码更加简洁和易于维护。

总之,在 React 中,兄弟组件之间的传值需要通过父组件作为中间层来实现。可以通过 propscontext APIRedux 等方式实现数据的传递和管理。

祖孙组件传值

Context 跨组件传递数据 【类似vue的 provide / inject】

1、首先,在父组件中创建一个 Contex对象:

import React from 'react';
const MyContext = React.createContext();

2、然后,在需要共享数据的组件的父组件中,通过 Provider 提供数据:

import React from 'react';
import MyContext from './MyContext';

function ParentComponent() {
  const sharedData = 'Hello, world!';

  return (
    <MyContext.Provider value={sharedData}>
      <ChildComponent />
    </MyContext.Provider>
  );
}

3、最后,在需要访问共享数据的组件中,可以使用 useContext 获取数据:

import React, { useContext } from 'react';
import MyContext from './MyContext';

function ChildComponent() {
  const value = useContext(MyContext);

  return <div>{value}</div>;
}

组件内常用的使用方式(类比vue,v-if,v-for试试例子)

类比v-if,react里的实现dom元素的切换显示隐藏

在React中,你可以使用state来控制组件的显示和隐藏。下面是一个简单的代码示例来切换一个div元素的显示和隐藏:

import React, { useState } from 'react';

function App() {
  const [show, setShow] = useState(false);

  function toggleDiv() {
    setShow(!show);
  }

  return (
    <div>
      <button onClick={toggleDiv}>Toggle Div</button>
      {show && <div>Some content to show</div>}
    </div>
  );
}

export default App;

初始状态设置为不显示元素。当按钮被点击时,toggleDiv函数会被调用并更新show状态,如果showtrue,则显示div元素,否则隐藏div元素。在JSX中使用{},可以将变量嵌入到标签中。如果showfalse,则{show && <div>Some content to show</div>}的内容会被忽略。

类比v-if-else,react里的实现多个dom元素之间的显示隐藏

React中,并没有 v-else 指令,但可以通过JavaScript中的条件运算符 ternary operator (三元运算符)来实现类似的效果。

例如,假设有一个条件为 showComponent 的状态,当为 true 时渲染 Component1,否则渲染 Component2,可以这样编写:

{ showComponent ? <Component1 /> : <Component2 /> }

上述代码中,{} 表示JSX中的JS表达式,其中使用三目运算符检查 showComponent 的状态,根据结果渲染相应的组件。

在React中也可以使用 if-else 语句来实现条件渲染,例如:

let component;

if (showComponent) {
  component = <Component1 />;
} else {
  component = <Component2 />;
}

return (
  <div>
    {component}
  </div>
);

以上代码中,根据 showComponent 的状态来分配 component 的值,然后在JSX中渲染该组件。

类比v-for,实现列表遍历展示

在React中,你可以使用map()方法来遍历列表,并将列表项渲染到页面上。

    import React from 'react';

    function App() {
      const list = ['item1', 'item2', 'item3'];

      return (
        <div>
          <h1>My List</h1>
          <ul>
            {list.map((item, index) => (
              <li key={index}>{item}</li>
            ))}
          </ul>
        </div>
      );
    }

    export default App;

在这个示例中,list变量是一个包含三个字符串的数组。使用map()方法遍历数组,并为每个项创建一个<li>元素,并在元素中显示该项的值。key属性是必须的,并且必须是每个列表项的唯一标识符。在此示例中,使用index作为key属性,但更好的做法是使用每个项的唯一标识符。

笔记来源

转载自:https://juejin.cn/post/7241394138258915384
评论
请登录