React开发技巧

一、使用use-immer简化对象和数组状态的更新

1、安装

npm i use-immer immer -D

2、更新数组

import { useState } from 'react';
import { useImmer } from 'use-immer';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, updateList] = useImmer(initialList);

  function handleToggle(artworkId, nextSeen) {
    updateList(draft => {
      const artwork = draft.find(a =>
        a.id === artworkId
      );
      artwork.seen = nextSeen;
    });
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

3、更新对象

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }

  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

官网案例

二、在同一次循环中执行多次状态更新

react中使用useState定义的状态变量,在单次更新循环中的更新其实是异步的,所以下面的setNum两次更新后,num还是等于1
代码如下:

const [num,setNum]=useState(0);
setNum(num+1);
setNum(num+1);

这时候我们可以使用更新函数的回调形式,来规避这个问题:

const [num,setNum]=useState(0);
setNum(prev=>prev+1);
setNum(prev=>prev+1);

上面 prev 是上次更新 num 后得到的值,每次拿到的都是最新的。

三、获取真实dom的方法

1、通过ref回调返回的el

export default function Comp(){
	const [dom,setDom]=useState(null);
	return <span ref={(span) => setDom(span)}>div</span>
}

2、refDom.current

如果是html标签的ref,refDom.current则直接返回的dom节点。
如果是组件,则返回的组件实例,内部含有forwardRefuseImperativeHandle暴露的对象或变量。

四、关于key的设置

jsx模版文件中直接使用map函数做渲染时,务必在mapreturn的最外层加上key设置。

return {
	arr.map((item) => {
	    return <div key={item["featureId"]}>["title"]}</div>;
	})
}

五、数组和对象状态操作

数组和对象状态值的修改、新增、删除都不应该直接修改变量本身,而是应该在原来数据基础上重新生成一个对象然后使用赋值函数进行状态更新。

六、不要在 state 中镜像 props

function Message({ messageColor }) {
  const [color, setColor] = useState(messageColor);

这里,一个 color state 变量被初始化为 messageColorprop 值。这段代码的问题在于,如果父组件稍后传递不同的 messageColor 值(例如,将其从 ‘blue’ 更改为 ‘red’),则 color state 变量将不会更新! state 仅在第一次渲染期间初始化。

七、state变量的互相引用

互相引用的state变量,本质还是独立的,被引用的对象的更新不会导致引用对象的数据变量

  const [items, setItems] = useState(initialItems);
  const [selectedItem, setSelectedItem] = useState(
    items[0]
  );

setSelectedItem

例如:items[0]的改变,不会导致selectedItem的改变。

八、相同位置的相同组件,state会被保留

相同位置的相同组件,组件被销毁重新创建过程中,state会被保留。
对 React 来说重要的是组件在 UI 树中的位置,而不是在 JSX 中的位置!
https://react.docschina.org/learn/preserving-and-resetting-state

九、useRef和createRef区别

  • createRef会在组件每次渲染的时候重新创建(任何state更新都会导致重新渲染)
  • useRef只会在组件首次渲染时创建

react之useRef和createRef

十、ref获取子组件引用

  1. 可以看到在父组件中定义了inputRef
  2. ref={inputRef}传递给子组件
  3. ref={ref}子组件赋值
import { forwardRef, useRef } from 'react';

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

十一、暴露子组件API

import {
  forwardRef, 
  useRef, 
  useImperativeHandle
} from 'react';

const MyInput = forwardRef((props, ref) => {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    // 只暴露 focus,没有别的
    focus() {
      realInputRef.current.focus();
    },
  }));
  return <input {...props} ref={realInputRef} />;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

十二、强制同步更新

默认情况下,我们在更改state之后,页面的更新是异步的。如果想要强制同步,我们可以使用react-dom下的flushSync

const [text, setText] = useState('');
function handleAdd() {
    flushSync(() => {
      setText('');
    });
    console.log("text",text);//js戴拿,这里不会立即更新
}

十三、Effect导致的死循环

const [count, setCount] = useState(0);
useEffect(() => {
  setCount(count + 1);
});

每次渲染结束都会执行 Effect;而更新 state 会触发重新渲染。但是新一轮渲染时又会再次执行 Effect,然后 Effect 再次更新 state……如此周而复始,从而陷入死循环。

Effect 通常应该使组件与 外部 系统保持同步。如果没有外部系统,你只想根据其他状态调整一些状态,那么 你也许不需要 Effect。

十四、useEffect的使用

useEffect(() => {
  // 这里的代码会在每次渲染后执行
});

useEffect(() => {
  // 这里的代码只会在组件挂载后执行
}, []);

useEffect(() => {
  //这里的代码只会在每次渲染后,并且 a 或 b 的值与上次渲染不一致时执行
}, [a, b]);

useEffect(() => {
  const node = ref.current;
  node.style.opacity = 1; // 触发动画
  return () => {
  	//这里一般放一些重置业务。组件销毁时,会先执行return中的业务,然后在挂在成功后,在执行useEffect中的业务。
  	//正常state更新导致的重新渲染,不会出发return中业务的执行。
    node.style.opacity = 0; // 重置为初始值
  };
}, []);

十五、useEffectEvent

useEffectEvent用于设置方法为非响应式,因此你不需要将它们指定为useEffect依赖。

function ChatRoom({ roomId, onReceiveMessage }) {
  const [messages, setMessages] = useState([]);

  const onMessage = useEffectEvent(receivedMessage => {
    onReceiveMessage(receivedMessage);
  });

  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    connection.on('message', (receivedMessage) => {
      onMessage(receivedMessage);
    });
    return () => connection.disconnect();
  }, [roomId]); // ✅ 所有依赖已声明
  // ...

十六、自定义Hook

我们在书写自定义Hook时,需要注意一下几点:

  1. 永远以use开头
  2. 自定义Hook中正常需要调用系统Hook,否则也就不用定义为自定义Hook。(技术上 React 对此并不强制要求。)
  3. 自定义Hook必须在函数式组件的顶层进行调用

官网案例:案例地址

你可能感兴趣的:(React,react.js,javascript,ecmascript)