跳转至

![[Pasted image 20240609154853.png]] ![[Pasted image 20240609155252.png]]

Keeping Components Pure#

To keep pure, we should keep changes out of the render phase.
![[Pasted image 20240609155801.png]]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { useState, useEffect } from "react";

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

  // Optional: to increment count whenever the component is rendered or updated
  useEffect(() => {
    setCount(count + 1);
  }, []);

  return <div>Message {count}</div>;
};

export default Message;

Updating Objects#

We should treat state objects as immutable and read only.
We don't modify any of the properties of the string object.
To tell React to update states, we have to give React a brand new object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function App() {
  // It is better to group related state variabls inside   an object.
  const [drink, setDrink] = useState({
    title: "Americano",
    price: 6,
  });

  const handleClick = () => {
    const newDrink = {
      // use the spread operator
      ...drink,
      price: 5,
    };
    setDrink(newDrink);
    =>
    setDrink({ ...drink, price:5 })
  };

  return (
    <div>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}

export default App;

Updating Nested Objects#

In React, state updates should be done immutably, meaning you should not directly modify the existing state object. Instead, you should create a new object with the updated values.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function App() {
  const [customer, setCustomer] = useState({
    name: "John",
    address: {
      city: "San Francisco",
      zipCode: 94111,
    },
  });

  const handleClick = () => {
    setCustomer({
      ...customer,
      address: { ...customer.address, zipCode: 94112 },
    });
  };

  return (
    <div>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}

export default App;

  1. Spread Operator:
  2. ...customer creates a shallow copy of the customer object.
  3. address: { ...customer.address, zipCode: 94112 } creates a shallow copy of the address object and updates the zipCode.
  4. Creating New Objects:
  5. By using the spread operator, you are not mutating the original customer object or its address property. Instead, you create new copies of these objects with the updated values.
  6. Updating State:
  7. setCustomer is called with the new object, ensuring that React detects the state change due to the new reference.

Updating Array#

Updating arrays in React follows the same immutability principles as updating objects. When updating an array, you should create a new array with the updated values rather than modifying the existing array directly. This ensures React can properly detect changes and trigger re-renders.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useState } from 'react';

const TagsComponent = () => {
  const [tags, setTags] = useState(['happy', 'cheerful']);

  // Add a new tag
  const addTag = (newTag) => {
    setTags([...tags, newTag]);
  };

  // Remove a tag by name
  const removeTag = (tagName) => {
    setTags(tags.filter((tag) => tag !== tagName));
  };

  // Update a tag by name
  const updateTag = (oldTag, newTag) => {
    setTags(tags.map((tag) => (tag === oldTag ? newTag : tag)));
  };

  return (
    <div>
      <ul>
        {tags.map((tag, index) => (
          <li key={index}>{tag}</li>
        ))}
      </ul>
      <button onClick={() => addTag('excited')}>Add Tag</button>
      <button onClick={() => removeTag('happy')}>Remove 'happy'</button>
      <button onClick={() => updateTag('cheerful', 'joyful')}>Update 'cheerful' to 'joyful'</button>
    </div>
  );
};

export default TagsComponent;

Updating Array of Objects#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { useState } from 'react';

const TagsComponent = () => {
  const [tags, setTags] = useState([
    { id: 1, name: 'happy' },
    { id: 2, name: 'cheerful' }
  ]);

  // Add a new tag
  const addTag = (newTag) => {
    setTags([...tags, newTag]);
  };

  // Remove a tag by name
  const removeTag = (tagName) => {
    setTags(tags.filter(tag => tag.name !== tagName));
  };

  // Update a tag by name
  const updateTag = (oldTagName, newTagName) => {
    setTags(tags.map(tag => 
      tag.name === oldTagName ? { ...tag, name: newTagName } : tag
    ));
  };

  return (
    <div>
      <ul>
        {tags.map(tag => (
          <li key={tag.id}>{tag.name}</li>
        ))}
      </ul>
      <button onClick={() => addTag({ id: tags.length + 1, name: 'excited' })}>Add Tag</button>
      <button onClick={() => removeTag('happy')}>Remove 'happy'</button>
      <button onClick={() => updateTag('cheerful', 'joyful')}>Update 'cheerful' to 'joyful'</button>
    </div>
  );
};

export default TagsComponent;

Summary of Updating Objects and Array#

Objects#

1
2
3
4
5
6
const [drink, setDrink] = useState({
  title: "Americano",
  price: 6,
});
...
setDrink({ ...drink, price: 5 })

Nested Objects#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const [customer, setCustomer] = useState({
  name: "John",
  address: {
    city: "San Francisco",
    zipCode: 94111,
  },
}); 

setCustomer({
    ...customer,
    address: { ...customer.address, zipCode: 94112 },
  })

Array#

1
2
3
4
5
6
7
const [tags, setTags] = useState(['happy', 'cheerful']);
// 增
setTags([...tags, 'exciting']);
// 删
setTags(tags.filter((tag) => tag !== 'happy'))
// 改
setTags(tags.map((tag) => tag === 'happy' ? 'happiness' : tag)

Array of Objects#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const [tags, setTags] = useState([
  { id: 1, name: 'happy' },
  { id: 2, name: 'cheerful' }
]);
// 增(还是一样的)
setTags([ ...tags, { id: 3, name: 'exciting' } ])
// 删(删去 id 为 1 的 tag)
setTags(tags.filter(tag => tag.id !== 1)
// 改(修改 id 为 1 的 tag 的 name)
setTags(tags.map(tag.id === 1 ? { ...tags, tag.name: 'sad' } : tag.name))

Simplify Update Logic with Immer#

npm install immer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import produce from 'immer'
setBugs(produce(draft => {
  const bug = draft.find(bug => bug.id === 1)
  if (bug) bug.fixed = true
  }))
````

## Sharing States between Components

Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, ==move it to their closest common parent==, and then ==pass it down to them via props==. This is known as **lifting state up**, and it’s one of the most common things you will do writing React code.

`App.tsx`
```tsx
import Cart from "./components/Cart";
import NavBar from "./components/NavBar";
import { useState } from "react";

function App() {
  const [cartItems, setCartItems] = useState(["product 1", "product 2"]);
  return (
    <div>
      <NavBar cartItemsCount={cartItems.length} />
      <Cart cartItems={cartItems} onClear={() => setCartItems([])} />
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}
export default App;

NavBar.tsx

1
2
3
4
5
6
7
8
9
interface Props {
  cartItemsCount: number;
}

const NavBar = ({ cartItemsCount }: Props) => {
  return <div>NavBar: {cartItemsCount}</div>;
};

export default NavBar;

Cart.tsx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
interface Props {
  cartItems: string[];
  onClear: () => void;
}

const Cart = ({ cartItems, onClear }: Props) => {
  return (
    <div>
      {cartItems.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
      <button onClick={onClear}>Clear</button>
    </div>
  );
};

export default Cart;

Exercise - Updating State#

Object

1
2
3
4
5
6
7
8
9
const [game, setGame] = useState({
  id: 1,
  player: {
    name: "John",
  },
});

const handleClick = () => {
  setGame({ ...game, player: { ...game.player, name: "Bob" } });

Array

1
2
3
4
5
6
7
8
9
const [pizza, setPizza] = useState({
  name: "Spicy Pepperoni",
  toppings: ["Mushroom"],
});

const handleClick = () => {
  // 增加一个 toppings
  setPizza({ ...pizza, toppings: [...pizza.toppings, "pineapple"] });
};

Nested Object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const [cart, setCart] = useState({
  discount: 0.1,
  items: [
    { id: 1, title: "Product 1", quantity: 1 },
    { id: 2, title: "Product 2", quantity: 1 },
  ],
});

const handleClick = () =>
  setCart({
    ...cart,
    items: cart.items.map((item) =>
      item.id === 1 ? { ...item, quantity: 2 } : item
    ),
  });

// Summary:
❌ setCart({...cart, items: [cart.items.map((item) => item.id === 1 ? item.quantity = 2 : item )]})

✔️ setCart({...cart, items: [cart.items.map((item) => item.id === 1 ? {...item, quantity: 2} : item )]})

Exercise - Expandable Text Component#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useState } from "react";

interface Props {
  children: string;
  maxChars?: number;
}

const ExpandableText = ({ children, maxChars = 100 }: Props) => {
  const [isExpanded, setExpaned] = useState(false);

  if (children.length < maxChars) return children;
  return (
    <>
      <p>
        {isExpanded ? children : children.slice(0, maxChars) + "..."}
        <button onClick={() => setExpaned(!isExpanded)}>
          {isExpanded ? "Less" : "More"}
        </button>
      </p>
    </>
  );
};

export default ExpandableText;

2024-06-09 15:39 2024-06-10 18:56

评论