React基本知识点整理

一、父子组件传值

1.1 父组件传子组件

1.1.1 组件为类组件时

由于子组件继承于Component,而Component函数实现了将props存入this的功能,所以可以在子组件直接调用super(props)使用。

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}
// 父組件 只展示主要代码
import React, { Component } from 'react'
import ChildrenClass1 from './components/class/childrenClass1'
export default class App extends Component {
	render() {
    return (
      <div>
        <ChildrenClass1 name="李靖"></ChildrenClass1>
      </div>
    )
  }
}
// 子組件
import { Component } from "react";

class ChildrenClass1 extends Component {
  constructor(props) {
    super(props)

    this.state = {
      count: 0
    }
  }

  render() {
    const { name } = this.props
    return (
      <div>
        <h2>我是类组件</h2>
        <h3>{this.state.count}</h3>
        <h3>{name}</h3>
      </div>
    )
  }
}

1.1.2 组件为函数组件时

函数组件就比较方便了,直接拿传入的参数来用就可以

// 父组件 仅展示主要代码
import React, { Component } from 'react'
import ChildrenFun1 from './components/function/childrenFun1'
export default class App extends Component {
  render() {
    return (
      // 
//

Hello App

//
//

{this.state.counter}

// //
//
<div> <ChildrenFun1 name="父组件传来的name"></ChildrenFun1> </div> ) } } // 子组件 function childrenFun1(props) { const { name } = props return ( <div> <h2>我是方法组件</h2> <h3>{name}</h3> </div> ) }

1.2. 子组件传递父组件

有的时候需要从子组件传递给父组件消息,同样需要用props来实现,只不过我们传的是回调函数:

// 父组件 主要代码
import { Component } from "react";
import CountBtn from '../function/countBtn'

class ChildrenClass1 extends Component {
  constructor(props) {
    super(props)

    this.state = {
      count: 0
    }
  }

  render() {
    const { name } = this.props
    return (
      <div>
        <h2>我是类组件</h2>
        <h3>{this.state.count}</h3>
        <h3>{name}</h3>
        <CountBtn btnClick={e => this.increase()}></CountBtn>
      </div>
    )
  }
  increase() {
    let current = this.state.count
    current++
    this.setState({
      count: current
    })
  }
}

// 子组件
function countBtn(props) {
  const { btnClick } = props
  console.log(btnClick);
  return (
    <button onClick={btnClick}>增加1</button>
  )
}
export default countBtn

二、在React中使用插槽

2.1 使用props.children来获取父组件信息

在React中,在组件中使用props.children可以获取该组件开始标签至结束标签之间的内容,可以用数组取值的方式获取到具体的内容。

// 父组件 主要代码
import React, { Component } from 'react'
import NavBar from './components/function/NavBar'
export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      counter: 1,
    }
  }
  render() {
    return (
      <div>
        <NavBar>
          <div>葛二蛋</div>
          <div>葛尔丹</div>
          <div>煮鸡蛋</div>
        </NavBar>
      </div>
    )
  }
 }
// 子组件
export default function NavBar(props) {
  return (
    <div>
      <h2>{props.children[0]}</h2>
      <h2>{props.children[1]}</h2>
      <h2>{props.children[2]}</h2>
    </div>
  )
}

由上可见,我们可以利用这种方法实现需求,但是仔细观察下也能发现其操作过于繁琐且无法清晰的定位具体的参数。所以接下来介绍一种更通用的方法

2.2 直接利用props传递

我们也可以将需要显示的内容作为变量直接传递给子组件:

// 父组件 主要代码
import React, { Component } from 'react'
import NavBar from './components/class/NavBar'
export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      counter: 1,
    }
  }
  render() {
    const slotText1 = '二郎神'
    const slotText2 = '茶盘'
    const slotText3 = '小玉'
    return (
      <div>
        <NavBar slotText1={slotText1} slotText2={slotText2} slotText3={slotText3}></NavBar>
      </div>
    )
  }
}

// 子组件
import { Component } from "react";

export default class NavBar extends Component {
  render() {
    const { slotText1, slotText2, slotText3 } = this.props
    return (
      <div>
        <h2>{slotText1}</h2>
        <h2>{slotText2}</h2>
        <h2>{slotText3}</h2>
      </div>
    )
  }
}

三、JSX语法

以下案例部分出自于React官网
官方网址

3.1 在React中使用点语法

当你在一个模块中导出许多 React 组件时,这会非常方便。例如,如果 MyComponents.DatePicker 是一个组件,你可以在 JSX 中直接使用:

import React from 'react';

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />;
}

3.2 用户自定义组件必须为大写字母开头

// 父组件 报错,改为大写则无报错
import React, { Component } from 'react'
import navBar from './components/class/navBar'
export default class App extends Component {
  render() {
    const slotText1 = <div>二郎神</div>
    const slotText2 = <div>茶盘</div>
    const slotText3 = <div>小玉</div>
    return (
      <div>
        <navBar slotText1={slotText1} slotText2={slotText2} slotText3={slotText3}></navBar>
      </div>
    )
  }
}
// 子组件为小写会报错
export default function navBar(props) {
  return (
    <div>
      <h2>{props.children[0]}</h2>
      <h2>{props.children[1]}</h2>
      <h2>{props.children[2]}</h2>
    </div>
  )
}

3.3 在运行时选择类型

在React中不能将通用表达式作为它的元素类型,比如当我们想动态的返回组件时,如果我们希望能实现这个功能,则应该将包含所有组件的整个模块存入一个大写字母开头的变量中:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 错误!JSX 类型不能是一个表达式。
  // return ;
  // 正确!JSX 类型可以是大写字母开头的变量。
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

3.4 JSX中的props

  • 字符串变量
    字符串变量可以直接赋给props,等同于{}取值;

  • props默认值为"true",但是不建议这样写,容易与ES6的语法冲突。比如{a}在ES6中式{a: a}的简写,在props中代表的是:{a: true}

  • props的展开:
    在JSX中,可以使用展开运算符...来传递整个props对象,还可以保留部分prop,将剩余的props继续传递下去:

    const Button = props => {
    	const { kind, ...other } = props;
    	const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
    	return <button className={className} {...other} />;
    }
    

3.5 JSX中的子元素

包含在开始标签和结束标签中的内容都可以用props.children属性来表示。根据书写格式不同,有几种不同的方法来传递子元素:

  • 字符串字面量
    当开始标签和结束标签中式是字符串时,那么props.children就代表这些字符串,JSX 会移除行首尾的空格以及空行。与标签相邻的空行均会被删除,文本字符串之间的新行会被压缩为一个空格。
  • 是其他的JSX元素
    如组件嵌套、字符串和html代码混合等,甚至可以直接返回数组中存储的元素。
    <main>
    	<component1></component1>
    	<component2></component2>
    </main>
    
    <div>
    	dog
    	<h2>123</h2>
    </div>
    

3.6 在jsx中传递对象

除了字符串、数字和其它 JavaScript 表达式,你甚至可以在 JSX 中传递对象。对象也用大括号表示,例如 { name: “张三”, age: 25 }。因此,为了能在 JSX 中传递,你必须用另一对额外的大括号包裹对象:person={{ name: “张三”, age: 25 }}。

export default function TodoList() {
  return (
    
); }

四、在react中使用refs

在开发中,我们又是会需要获取DOM进行操作,这时就需要用到refs了
在react中使用refs的方式大致有三种:

import React, { Component, createRef } from 'react'

export default class App extends Component {
  constructor(props) {
    super(props)
    this.divRef2 = createRef()
    this.divRef3 = null
    this.state = {
      counter: 1
    }
  }
  render() {
    return (
      <div>
        <h2></h2>
        <div ref="ref1">ref1</div>
        <div ref={this.divRef2}>ref2</div>
        <div ref={element => this.divRef3 = element}>ref3</div>

        <button onClick={e => this.changeText()}>修改上方文字</button>
      </div>
    )
  }
  
  changeText() {
    this.refs.ref1.innerHTML = '第一种' // 已弃用,不建议使用
    this.divRef2.current.innerHTML = '第二种'
    this.divRef3.innerHTML = '第三种'
  }
}

五、react中的SCU优化及类似优化

5.1 shouldComponentUpdate函数

在我们不做任何处理时,当修改组件的state中的数据后,不仅该组件会调用render函数,所有的子类组件也会调用本身的render函数(函数组件自身再被调用一次)。显而易见,很多时候这样做是没有必要的,因为在父组件中修改的内容可能根本没有在子组件中用到。也正是因为这样,我们需要对子组件中的render函数进行控制,所以就用到了shouldComponentUpdate这个生命周期函数。

该函数有两个入参:

  • nextProps: 修改后的最新props;
  • nextState: 修改后的最新state数据;

该函数返回值:

  • true: 执行render函数;
  • false: 不执行render函数;
    默认返回true
    所以,我们可以在这个函数中根据实际情况进行判断是否执行render函数:
// 父组件
export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      counter: 1,
      message: '天下第一',
    }
  }
  render() {
    return (
      <div>
        <Banner name={this.state.message}></Banner>
        <button onClick={e => this.changeCounter()}>+</button>
        <h2>{this.state.counter}</h2>
        <button onClick={e => this.changeMessage()}>改变message</button>
        <h2>{this.state.message}</h2>
      </div>
    )
  }
  changeCounter() {
    const newCounter = this.state.counter + 2
    this.setState({
      counter: newCounter
    })
  }
  changeMessage() {
    this.setState({
      message: '我开玩笑的'
    })
  }
}
// 子组件
import { Component } from "react";

export default class Banner extends Component {
  render() {
    return (
      <div>{this.props.name}</div>
    )
  }
  componentDidUpdate() {
    console.log('组件Banner更新了');
  }
  shouldComponentUpdate(prevProps, nextState) {
    // 在该函数下可以控制子组件是否更新,返回true则调用render函数,反则不调用,可以在此根据实际需求判断
    if (prevProps.name !== this.props.name) {
      return true
    }
    return false
  }
}

结果:当点击+时,子组件不会触发componentDidUpdate()方法,当点击改变message按钮后,子组件触发了更新。

5.2 PureComponent和memo

上面的方式在组件很少的时候是合适的,但是如果我们的项目中存在很多组件,那么就需要写很多的shouldComponentUpdate()函数,这无疑使繁琐且容易遗漏的,所以,react帮我们实现了另外的方式:PureComponent和memo。

  • 针对类组件
    react实现了PureComponent类,只要我们的组件继承了它,就可以实现类似shouldComponentUpdate的效果,而且还不用再手动判断。
    我们可以发现shouldComponentUpdate()其实就是判度state或props的数据是否改变了,而PureComponent内部就已经做到了这点,它对state或者props的数据进行修改前后的比对,只有当前组件内使用的数据更改,才会触发render函数

    import { PureComponent } from "react";
    
    export default class Banner extends PureComponent{
    	render() {
    	return (
    		<div>{this.props.name}</div>
    		)
    	}
    	componentDidUpdate() {
    		console.log('组件Banner更新了');
    	}
    }
    
  • 针对函数式组件
    对于函数式组件来说,由于无法继承,所以react为其设计了memo高阶组件,只要用其包裹函数式组件即可:

    // 使用高阶组件memo包裹函数式组可以实现类组件继承PureComponent那样的效果,只有
    const Banner2 = memo(function() {
      console.log('调用Banner2函数组件');
      return (
        <div>我是函数式组件2</div>
      )
    })
    // 没有用memo包裹的函数式组件,每次父组件更新都会被调用
    function Banner3() {
      console.log('调用Banner3函数组件');
      return (
        <div>我是函数式组件3</div>
      )
    }
    

六、条件渲染

6.1 使用if来决定渲染

在React中,可以使用if判断来决定渲染哪个组件或元素

function Component1(props) {
  return <h1>组件一</h1>;
}

function Component2(props) {
  return <h1>组件二.</h1>;
}

function Greeting(props) {
  const flag = props.flag;
  if (flag) {
    return <Component1 />;
  }
  return <Component2 />;
}

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting flag={false} />,
  document.getElementById('root')
);

另外,我们可以通过在组件中返回null来阻止组件渲染,即使该组件已被其他组件引用(一般不推荐,会使得代码难以理解,通常会在父组件就过滤掉不展示得项):

function Component3(props) {
  if (!props.flag) {
    return null;
  }

  return (
    <div>
      Component3!
    </div>
  );
}

// 伪代码
render() {
    return (
      <div>
        <Component3 flag={false} />
      </div>
    );
  }

6.2 使用三元运算符来渲染

{ flag ? <h2>标题2</h2> : <h1>标题1</h1> }

6.3 使用&&运算符来渲染

在React中可以使用&&运算符来判断渲染哪些元素,当&&左侧的值为真值时,则渲染右侧的元素,如:

{ flag && <h1>标题1</h1> }

上面的例子中。flag为真时,则页面会渲染"标题1"

注意:当不要把flag的值设为数字0,不然,整个表达式会变成左边的值,即0,React会渲染这个值而不是不渲染。

七、列表渲染

7.1 实现方式

列表渲染一般使用数组的filter和map实现,如:

// 伪代码
const immortals = [
  '孙悟空',
  '二郎神',
  '三太子',
  '四天王',
  '太白星',
];
const immortalItem = immortals.map(immortal => <li>{immortal}</li>)

return (
	<ul>{immortalItem}</ul>
)

八、React中的纯粹组件

(纯粹?纯粹武夫)

8.1 纯函数

在React中,偏向于使用纯函数,纯函数的特征有:

  • 只负责自己的任务。它不会更改在该函数调用前,就已存在的对象或变量。
  • 输入相同,则输出相同。给定相同的输入,纯函数应总是返回相同的结果。
    通俗的来说,就是调用这个函数的结果是稳定的,不会发生突变的情况,这也是React正常运行的基础。以下代码为示例:
// 纯函数
// 此函数无论传入什么数值,都只会返回该数值的2倍值
let a = 2;
function add(a) {
	return a * 2
}
add(a)  // 4
add(a)  // 4

8.2 在React中纯函数的应用

值得注意的是,React就是围绕纯函数的概念进行设计的,也就是说,React会默认我们写的函数都是纯函数,不按照这个规则写,那么就会产生副作用

副作用

副作用,在React的渲染过程中可能产生不符合预期的结果,如下例:

// 非纯函数
// 可以看到,由于以下函数修改了传入参数的值,所以结果具有不稳定性
let a = 0
function getNum(a) {
	a = a + 1
}
getNum(a) // 1
getNum(a) // 2
getNum(a) // 3

所以在React的渲染期间,我们应该尽可能的避免副作用

// 下面是React官网的一个例子,我们在React中运行这段代码会报错,因为我们在渲染过程中产生了副作用,
// 代码预编译阶段可能无法获取到dom
export default function Clock({ time }) {
  let hours = time.getHours();
  if (hours >= 0 && hours <= 6) {
    document.getElementById('time').className = 'night';
  } else {
    document.getElementById('time').className = 'day';
  }
  return (
    <h1 id="time">
      {time.toLocaleTimeString()}
    </h1>
  );
}

// 正确的做法应该是动态的传入类名
export default function Clock({ time }) {
  let hours = time.getHours();
  let className;
  if (hours >= 0 && hours <= 6) {
    className = 'night';
  } else {
    className = 'day';
  }
  return (
    <h1 className={className}>
      {time.toLocaleTimeString()}
    </h1>
  );
}

注意
但是并不是说任何时候都不要写副作用的函数,我们仅需要尽可能的保证在渲染过程中不产生副作用,而在处理事件时,我们是需要使用非纯函数的。比如更改数据、改变动画等。
如果你用尽一切办法,仍无法为副作用找到合适的事件处理程序,你还可以调用组件中的 useEffect 方法将其附加到返回的 JSX 中。这会告诉 React 在渲染结束后执行它。然而,这种方法应该是你最后的手段。

九、React中的交互

9.1 响应事件

React可以在JSX中添加事件处理函数,它将在相应交互时触发:

// 案例来自React官网
export default function Button() {
  function handleClick() {
    alert('你点击了我!');
  }

  return (
    <button onClick={handleClick}>
      点我
    </button>
  );
}

如上所示,我们可以定义一个函数,然后将其通过props的方式传入元素中作为其交互函数。
需要注意的是:我们传入的是一个处理函数,但不需要立即执行,所以不要再后面加上()。如本例中传入"handleClick()"的话,只会在页面渲染时出触发一次,后面再次点击将不会触发。

9.1.1 命名事件处理函数 prop

html原始的元素可能支持特定的事件名称,如button的onClick事件,但是当我们创建自己的组件时,就可以通过prop传入自定义的事件名称,使代码可读性更高:

// 案例来自React官网
function Button({ onSmash, children }) {
  return (
    <button onClick={onSmash}>
      {children}
    </button>
  );
}

export default function App() {
  return (
    <div>
      <Button onSmash={() => alert('正在播放!')}>
        播放电影
      </Button>
      <Button onSmash={() => alert('正在上传!')}>
        上传图片
      </Button>
    </div>
  );
}

如上例所示,我们封装了自定义组件Button,然后在父组件调用该组件时,传入了自定义的事件处理函数名,这样就可以更好地区分当前组件的功能了。

9.1.2 事件传播

我们知道,在点击html元素时,会存在冒泡现象,即点击子元素,不仅会触发子元素的onClick事件,同事还会向上传递,引发父元素的onClick事件,一直到根元素,但是有的时候我们不需要这种特性,这时候该怎么办呢?其实也很简单,在原生的js中我们使用e.stopPropagation()就可以解决,在React中也是同理:

function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      e.stopPropagation();
      onClick();
    }}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('你点击了 toolbar !');
    }}>
      <Button onClick={() => alert('正在播放!')}>
        播放电影
      </Button>
      <Button onClick={() => alert('正在上传!')}>
        上传图片
      </Button>
    </div>
  );
}

9.1.3 阻止默认事件

和事件冒泡同理,有的时候我们也想禁止默认的事件,比如a标签,点击会触发href属性跳转,我们想禁止这个属性,那么就可以使用e.preventDefault()来实现,在React中也是如此:

function handleClick(e) {
	e.preventDefault();
	alert('自定义跳转')
}
export default function Signup() {
  return (
    <a href="www.baidu.com" onClick={handleClick}>跳转</a>
  );
}

9.2 在React中使用state

9.2.1 为什么需要使用state

  • 在React中,局部变量无法在多次渲染中持久保存。 当 React 再次渲染这个组件时,它会从头开始渲染——不会考虑之前对局部变量的任何更改。
  • 更改局部变量不会触发渲染。 React 没有意识到它需要使用新数据再次渲染组件。

9.2.2 state的使用方法

为了解决上述的问题,React中引入了useState这个Hook。
这个hook返回的state变量可以用于保存渲染中的值,返回的state setter函数用于更改变量并处罚React渲染。

// 先引入这个hook
import { useState } from 'react';
// 利用useState将值存入state中
const [ num, setNum ] = useState(0)
// 修改num的值,修改后将触发React的渲染
function handleChangeNum() {
	setNum(num + 1)
}

9.2.3 使用state时的注意事项

  • useState是一个hook,而所有的Hook只能在组件或者自定义Hook的最顶层使用,所以不要在条件语句、循环语句或其他嵌套函数中使用Hook
  • state是私有的,也就是说每个组件都可以有自己独立的state,引入俩次相同的组件,修改其中一个的state并不会影响到另一个,且其父组件也无法改变这个值;
  • State 变量仅用于在组件重渲染时保存信息。在单个事件处理函数中,普通变量就足够了。当普通变量运行良好时,不要引入 state 变量。

9.2.4 state的特性

9.2.4.1 设置state会触发渲染

在React中,使用"setState"方法会导致页面的渲染,所以如果我们想要页面对我们的输入做出反应,那么就应该使用该方法。
但同时,我们也能从实际操作中得知,当我们修改state后,它是有一个延时性的,也就是说,设置 state 只会为下一次渲染变更 state 的值,比如以下的例子,在我们更新state中number的值后,alert得出的number的值并没有改变:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        alert(number); // 0
      }}>+5</button>
    </>
  )
}
9.2.4.2 state如同一张快照

““正在渲染” 就意味着 React 正在调用你的组件——一个函数。你从该函数返回的 JSX 就像是 UI 的一张及时的快照。它的 props、事件处理函数和内部变量都是 根据当前渲染时的 state 被计算出来的。“——React官网
在React中,当我们调用一次useState时,React就会创建这次渲染的一张state快照,并将其传入组件。所以,上面案例的情况就很好理解了,由于本次的state快照使用的值是0,所以alert出来的值就是0,到下次渲染时生成的state快照中,number的值就是5了,以此类推。
一个 state 变量的值永远不会在一次渲染的内部发生变化,即使事件处理程序是异步的,所以下面的例子中,number还是0。

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setTimeout(() => {
          alert(number);
        }, 3000);
      }}>+5</button>
    </>
  )

9.2.5 state的状态更新函数

从之前的学习中,我们知道,一般情况下,本次渲染的结果要在下一次调用渲染函数时才生效,那么如果我们多次调用useState函数,会多次渲染吗?

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

答案是不会,实际上number在state中最终结果还是1,实际上不论调用多少次setNumber,我们也只触发了一次点击事件,这还是在一次渲染中,React 会等到事件处理函数中的 所有代码都运行完毕再处理你的 state 更新。 这就是重新渲染只会发生在所有这些 setNumber() 调用之后的原因。
那么,有没有什么办法能在一次事件中批处理更改state呢?其实是有的,我们不能往state中直接传递state值了,应该传递一个更新函数,例如这样:

setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);

这样的话就可以正常展示了。

9.2.6 更新state中的对象

state除了可以保存数字、字符串这类基本数据类型,也可以保存对象,但是我们不应该使用setState直接改变对象(由于对象是引用类型等原因),而是应该创建一个新的对象,将其作为副本,在setState中修改此副本即可,如下所示:

import { useState } from "react";

export default function cahngeObject1() {
  const initalPersonInfo = {
    age: 88,
    height: 1.88,
    name: 'Tom'
  }
  const [person, setPerson] = useState(initalPersonInfo)

  function handleChangePersonName() {
    setPerson({
      name: 'Peter',
      age: 88,
      height: 1.88
    })
  }

  return (
    <>
      <div>个人信息:</div>
      <ul>
        <li>姓名:{person.name}</li>
        <li>年龄:{person.age}</li>
        <li>身高:{person.height}</li>
      </ul>
      <button onClick={() => handleChangePersonName()}>改变信息</button>
    </>
  )
}

我们还可以使用展开符来简化这种对象更新的方式

// 可以用展开符来简化代码
setPerson({
  ...person,
  name: 'Peter'
})

然而,如果我们需要使用到嵌套对象时,展开的写法就有点繁琐了:

// 伪代码
const [person, setPerson] = useState({
    name: 'Tony',
    message: {
      hobby: 'play game',
      country: 'America',
      height: 1.73
    }
  })

  setPerson({
    ...person,
    message: {
      ...person.message,
      hobby: 'basketball'
    }
  })

所以我们可以用一种更方便的方法:引入Immer来简化代码逻辑,可以看到,使用immer可以极大的简化代码,而且也比较符合我们平时修改对象的逻辑:

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

export default function useImmerChangeObject() {
  const [person, setPerson] = useImmer({
    name: 'Tony',
    message: {
      hobby: 'play game',
      country: 'America',
      height: 1.73
    }
  })

  function handleChangePerson() {
    setPerson(draft => {
      draft.message.hobby = 'basketall'
    })
  }

  return (
    <>
      <div>个人信息:</div>
      <ul>
        <li>姓名:{person.name}</li>
        <li>国家:{person.message.country}</li>
        <li>身高:{person.message.height}</li>
        <li>爱好:{person.message.hobby}</li>
      </ul>
      <button onClick={() => handleChangePerson()}>改变部分信息</button>
    </>
  )
}

总结:React中的状态应该视为不可变;要创建一个新的对象用于设置状态;可以使用展开符来复制对象(注意嵌套对象);可以使用Immer来简化代码逻辑。

9.2.7 更新state中的数组

与更新state中的对象一样,我们在应该创建一个新的数组来执行相应的更新操作。

9.2.7.1 向数组中添加元素
let nextId = 2
const [ artists, setArtists ] = useState([
	{id: 0, name: 'Tom'},
	{id: 1, name: 'Mary'},
	{id: 2, name: 'Bob'}
])
setArtists( // 替换 state
  [ // 是通过传入一个新数组实现的
    ...artists, // 新数组包含原数组的所有元素
    { id: nextId++, name: 'Hufu' } // 并在末尾添加了一个新的元素
  ]
);
9.2.7.2 在数组中删除元素

可以使用filter函数来获取删除某项后的所有数组

// 伪代码
const [ gameList, setGameList ] = useState([
  { id: 0, name: '艾尔登法环' },
  { id: 1, name: '荒野大镖客' },
  { id: 2, name: '塞尔达传说:荒野之息' }
])

function handleDetele(id) {
	setGameList(gameList.filter(game => game.id !== id))
}

return (
	<>
	  <ul>
        {gameList.map(game => {
          return (
            <li key={game.id}>
              <span style={{marginRight: '12px',textAlign: 'left'}}>{game.name}</span>
              <button onClick={() => handleDetele(game.id)}>删除</button>
            </li>
          )
        })}
      </ul>
	</>
)
9.2.7.3 在数组中插入元素
// 伪代码
const [current, setCurrent] = useState('')
  const [ gameList, setGameList ] = useState([
    { id: 0, name: '艾尔登法环' },
    { id: 1, name: '荒野大镖客' },
    { id: 2, name: '塞尔达传说:荒野之息' }
  ])

  function insertGame() {
    let insertIndex = 1 // 可以是任意顺序
    setGameList([
      ...gameList.slice(0, insertIndex),
      { id: nextId++, name: current },
      ...gameList.slice(insertIndex)
    ])
    setCurrent('')
  }
9.2.7.4 替换数组中的元素
// 伪代码
const [ gameList, setGameList ] = useState([
    { id: 0, name: '艾尔登法环', heat: 1 },
    { id: 1, name: '荒野大镖客', heat: 1 },
    { id: 2, name: '塞尔达传说:荒野之息', heat: 1 }
  ])

  function handleIncrease(data) {
    setGameList(gameList.map(game => {
      if (game.id === data.id) {
        return { ...game, heat: game.heat + 1 }
      } else {
        return game
      }
    }))
  }
9.2.7.5 使用Immer简化代码

和更新对象一样,我们也可以使用Immer插件来简化更新数组的代码,使用Immer后,我们甚至可以使用诸如pop,shift等修改原数组的方法,Immer会自行做相应处理以保证不会影响到原数组。

import {useImmer} from 'use-immer'

export default function useImmerChangeArray() {

  const initalGameList = [
    { id: 0, name: '艾尔登法环', heat: 1 },
    { id: 1, name: '荒野大镖客', heat: 1 },
    { id: 2, name: '塞尔达传说:荒野之息', heat: 1 }
  ]
  const [myGameList, setMyGameList] = useImmer(initalGameList)
  const [ yourGameList, setYourGameList ] = useImmer(initalGameList)

  function handleIncreaseMyList(data) {
    setMyGameList(draft => {
      const item = draft.find(game => game.id === data.id)
      item.heat++
    })
  }

  function handleIncreaseYourList(data) {
    setYourGameList(draft => {
      const item = draft.find(game => game.id === data.id)
      item.heat++
    })
  }

  return (
    <>
      <div>我的游戏列表:</div>
      <ItemList gameList={myGameList} handleIncrease={handleIncreaseMyList}></ItemList>

      <div>你的游戏列表</div>
      <ItemList gameList={yourGameList} handleIncrease={handleIncreaseYourList}></ItemList>
    </>
  )
}

export function ItemList({gameList, handleIncrease}) {
  return (
    <>
      <ul>
        {gameList.map(game => {
          return (
            <li key={game.id}>
              <span style={{marginRight: '12px',textAlign: 'left'}}>{game.name}</span>
              <span>{game.heat}</span>
              <button onClick={() => handleIncrease(game)}>为它助力</button>
            </li>
          )
        })}
      </ul>
    </>
  )
}

你可能感兴趣的:(react,react.js,javascript,前端)