> 文章列表 > React 用一个简单案例体验一遍 React-dom React-router React-redux 全家桶

React 用一个简单案例体验一遍 React-dom React-router React-redux 全家桶

React  用一个简单案例体验一遍 React-dom  React-router  React-redux 全家桶

一、准备工作

本文略长,建议耐心读完,每一节的内容与上一节的内容存在关联,最好跟着案例过一遍,加深记忆。

1.1 创建项目

  • 第一步,执行下面的命令来创建一个 React 项目。
npx create-react-app react-example
cd react-example
  • 第二步,安装依赖,运行项目
yarn install 或 npm install
yarn start 或 npm run start

1.2 项目结构

如图:
项目结构

1.3 初始化

src/index.js 的默认代码删掉,保留下面这部分。

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById("root"));
const App = () => {return <div>Hello</div>
}
root.render(<App />);

现在项目看起来就像这样,一个简单的 Hello
请添加图片描述

二、React 的基本用法

如果你还不熟悉 React 的基础语法,可以阅读我前面写的 React & 工作日常语法。

1.1 输出 Hello, world

第一步肯定是要先来句 Hello,world!

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';const root = ReactDOM.createRoot(document.getElementById("root"));
const App = () => {return <div>Hello, world!</div> // 改动点
}
root.render(<App />);

请添加图片描述

1.2 组件的使用

组件可以由函数或者类来进行创建,像上面的 App 函数就是一个组件,里面可以混合写 HTML & JS 代码,React 会自动帮我们解析这些语法,像这种写法就叫就 JSX

下面我将以函数的方式来定义组件作为案例,类组件在讲生命周期时做演示。

  • 第一步,随便定义一个叫 List 的组件,内容随便写,比如 Hi List
const List = () => {return <div>Hi List.</div>
}
  • 第二步,引入 List,目前入口只有 App,很明显只能放这里。
const App = () => {return <div>Hello, world! </List> </div>
}

效果:
请添加图片描述

1.3 组件嵌套组件

App 组件嵌套 List 组件,我们管这叫父子组件,App 是父组件, List 是子组件,当 List 套上 Item 组件时,那 Item 的父组件就是 List,而祖父组件是 App, 以此类推。

现在我们来给 List 嵌套一个 Item 组件。

const List = () => {// 多行时要用 () 包裹return (<ul><Item/> // 引入</ul>)
}
const Item = () => {return <li>item1</li>
}

效果:
请添加图片描述

别忘了还可以写 JSX 的,现在试试用 Array.map 循环多个 Item 组件。

const List = () => {return (<ul>{[1, 2, 3, 4].map((num) => Item(num) )}</ul>)
}
const Item = (num) => {return <li key={num}>item{num}</li>
}

在看效果前思考下,上面代码具体会发生哪些变化?

  1. Item 函数组件多了个 num 参数,我们用它来接受,并用 {num} 括号包括起来进行访问
  2. <Item/> 的调用方式变成 Item(num) ,因为我们要将 num 值传递给 Item 函数,所以我们又发现了一个特性,在不传参的情况下直接声明 <Item/> 是会自动触发函数的。
  3. 循环组件时,key 属性得带上且是唯一的。

效果:
请添加图片描述

1.3 组件绑定点击事件

干巴巴的列表,没有交互行为怎么能行呢,
现在我们就来定义一个事件,当点击某个 Item 时提示对应的 num 数值,代码如下:

const onClickItem = (num) => {alert(num);
}
const Item = (num) => {return <li key={num} onClick={() => onClickItem(num)}>item{num}</li>
}

解释:

  1. 点击事件用 onClick 驼峰形式来表示,其它事件也是类似的,比如 onFocus, onMouse 等。
  2. onClick={() => 函数} 花括号返回一个箭头函数,函数里面就是返回我们定义的函数,后面点击时会触发。

不用怀疑语法是否有问题,连 JSX 语法都有了,你还在乎这个?

效果:
请添加图片描述

1.5 响应式数据

光弹窗没啥用啊,要不点击后直接变更 Item 的数据如何?
问题是… 怎么变更?直接将 num = xxx?熟悉 JS 的朋友应该知道,当 num 参数传递的是【基本类型】时只是一个副本,更改后对原来的 num 是无效的。
就算有效,你又如何将视图中的 num 也发生变化呢? 看来还是得借助 React 提供的语法了。
唉没办法,学吧~

  • 第一步,先将原来的 [1,2,3,4]useState 提供的函数来定义并导出 arr 变量数组,如图:
    请添加图片描述
    效果还是与原来一样,这里就不贴图了。

  • 第二步,在 arr 后面再声明一个 setArr 函数,名字随便定义,但一般用 set 开头作为规范好点,表示用来变更 arr 数据的。

const List = () => {const [arr, setArr] = useState([1, 2, 3, 4])
// ...省略
  • 第三步,触发 setArr 函数,更改 arr 数组里的数据来达到我们想要的视图变更效果
    请添加图片描述
    由于 setArr 是在 List 函数组件里定义的,其它函数无法直接访问,得通过传参的方式带过去、一直传到 onClickItem 中,有点绕,将就一下吧,相信你不介意的(/逃)。

这里我们重点关注这一段代码:

setArr(state => {const arr = [...state];arr[1] = 1000;return arr;
})

当点击后,将 arr[1] 改为 1000,并将新的 arr 返回出去,效果如图:
请添加图片描述
你可能注意到,state 就是原先的 arr,然后我们用扩展符号将它拷贝一份到新的 arr 中,为什么要这样做呢?直接 state[1] = 1000 再 return 出去不行吗,多此一举嘛这不是。

是可以这样做,数据也会发生变化了,但视图不会发生变化,因为 React 明确规定:state 是不可变数据,你不能直接改变它,而是要用一份副本去改变

为什么 state 要坚持不可变原则呢?官方也说了,当你要实现一个撤销&恢复的功能时就没辙了,或者说实现起来更复杂?React 这时要是还允许你去那岂不是有点不地道了,这不跟吃了上顿不考虑下顿的道理嘛。

小结:

  1. useState 可以声明响应式数据。
  2. state 数据不可变,要用副本代替,遵循不可变原则。

1.6 什么是钩子函数 Hook Function

其实我们已经用过钩子函数了,上面的 useState 就是一个钩子函数,React 内置了许多的钩子,比如 useEffect, useRef 等,这里就不一一介绍,只需明白以 use 为开头的均属于钩子函数即可。

我们也可以自定义钩子,问题来了,在什么样的场景下需要呢?里面写啥呢?
这个其实没有唯一答案,每个人对理解钩子的程度是不同的,导致有千千万万种类型的钩子

我个人更喜欢将它归为处理脏活的一类函数,更通俗点来说,是用来处理响应式数据的,比如,有个奖品业务的功能模块,针对这个模块,可能有验证奖品配置的逻辑,那么我就会给这个奖品加上几个 hook 函数,以便后续方便调用。

const usePrize = () => {const verifyPrizeConfig = (state) => {// Do something ...}const resetPrizeConfig = (state) => {// Do something ...}return [verifyPrizeConfig,resetPrizeConfig,]
}
const [ verifyPrizeConfig ] = usePrize();

1.7 组件的生命周期

每个组件渲染时,React 会逐步按顺序触发一些内置函数,这些被称为”生命周期函数“,我们可以根据不同周期函数来做一些业务处理,比如我想在组件渲染前先请求接口得到数据。

这里需要注意,函数组件是没有命周期函数的,只有类组件才有,既然这样,我们将 App 这个组件变成类组件即可,其它保持不变,谁规定类组件里面就不能嵌套函数组件

  • 第一步,将 App 函数组件改为类组件,如下:
// 源 App 函数组件
// const App = () => {
//   return <div>Hello, world! <List/> </div>
// }
// 新 APP 类组件
class App extends React.Component {constructor(props) {super(props);}render() {return <div>Hello, world! <List /> </div>}
}
  • 第二步,添加生命周期函数,这里用最常见的 componentDidMount 函数,表示组件第一次渲染时提前触发。
class App extends React.Component {// 省略componentDidMount() {// Do something...alert('Init')}
}

请添加图片描述

1.8 函数组件模拟生命周期

你可能想,这不公平,函数组件凭啥没有周期函数?别急,React 提供的钩子函数 useEffect 就派上用场了,该钩子完全可以模拟生命周期的三大核心函数:

componentDidMount() {} 组件第一次渲染时,就刚刚用到的
componentDidUpdate() {} 组件数据更新时(state 更新)
componentWillUnMount() {} 组件销毁时

使用方式也很简单,这里以 List 组件作为案例,不能忘记我们的老伙伴~

  • 第一步,模拟 componentDidMount,如下:
import { useState, useEffect } from 'react';
const List = () => {const [arr, setArr] = useState([1, 2, 3, 4]);// 模拟 componentDidMountuseEffect(() => {alert('List init') });return (<ul>{arr.map((num) => Item(num, setArr) )}</ul>)
}

请添加图片描述

  • 第二步,模拟 componentDidUpdate,在 useEffect 的第二个参数监听 state:
// ... 省略
const [arr, setArr] = useState([1, 2, 3, 4])useEffect(() => {alert('List init')}, [arr]) // componentDidUpdate

当点击更新数据时,这个函数就会再次触发,如图:
请添加图片描述
请添加图片描述

  • 第三步,模拟 componentWillUnMount,在 useEffect 里面 return 一个函数即可。
const [arr, setArr] = useState([1, 2, 3, 4])useEffect(() => {alert('List init')return () => { // componentWillUnMountalert('List destroyed!');};}, [arr])

componentWillUnMount 什么时候触发呢?实际上,每当 state 更新时,组件会重新渲染一次,这属于销毁行为,因此当点击更新数据后,会先触发 componentWillUnMount ,再触发 componentDidUpdate 。
效果:

请添加图片描述
请添加图片描述
请添加图片描述

三、React-router-dom

1.1 什么是 React-router-dom

一个页面怎么够用呢,现在我们想要通过点击 item 进入另一个页面,且保持页面不刷新,这里便可以用 React 提供的插件 React-router-dom ,俗称路由来实现。

1.2 React-router-dom 和 React-router 版本的区别

React-router-dom 是基于 React-router 改造的新版本,现在大家常用的版本是 React-router-dom
本案例将用 React-router-dom 来作为演示。

1.3 React-router-dom 的使用

  • 第一步,下载 react-router-dom。
yarn add react-router-dom
  • 第二步,在 src/index.js 中引入。
import {createBrowserRouter,RouterProvider,
} from "react-router-dom";
  • 第三步,将我们之前的 App 组件引入方式重新改造一下
// 旧代码
// root.render(<App />);
// 新代码
const router = createBrowserRouter([{path: "/",element: <App/>,},
]);root.render(<RouterProvider router={router} />);

现在效果和原来没区别,这里解释下步骤:

  1. 将原来 root.render 里的 App 替换成 RouterProvider 组件
  2. createBrowserRoute 里面的 element 引入 App。
  3. path (路由),当访问 / 根目录时才会渲染 App 组件。
  • 第四步,新建 AppDetail 组件并挂载到 /detail 路由上。
const AppDetail = () => {return <h1>Detail data</h1>
}
const router = createBrowserRouter([{path: "/",element: <App/>,},// 挂载到 /detail 路由{path: "/detail",element: <AppDetail/>,},
]);
  • 第五步,访问 /detail 看看效果
    请添加图片描述
  • 第六步,以点击跳转的方式访问 /detail,这里用到 router 提供的 <Link> 标签。
import {createBrowserRouter,RouterProvider,Link,
} from "react-router-dom";
const Item = (num, setArr) => {return (<li key={num} onClick={() => onClickItem(num, setArr)}><Link to='/detail'>item{num}</Link></li>)
}

效果:
请添加图片描述

react-router-dom 就这么简单,至于其它 API 的使用可以参考文档,这里不过多讲解。

四、React-redux

1.1 什么是 React-redux

React-redux 可以用来管理全局状态 state 的工具,使得组件之间可以访问同一份 state。

1.2 什么时候用 React-redux

当多个组件重复使用同一份 state 时就可以考虑用 redux 将它提升到全局中,以便于维护。

1.3 React-redux 下载&配置

  • 第一步,下载 react-redux,这里官方还提供了一个工具包 @reduxjs/toolkit,里面包含了 react-redux 所有功能以及内置一些其它功能,是官方极力推荐的,这两个一起下载。
yarn add react-redux @reduxjs/toolkit
  • 第二步,在 src 下新建 store/index.js 文件,负责管理全局 state,初始化内容如下:
// ./src/store/index.js
import { configureStore, createSlice } from '@reduxjs/toolkit'
const userSlice = createSlice({name: 'User', // name 必填的,当前作用域的标识符,可以理解为 nameSpace 命名空间,否则页面上无法正常展示。initialState: { // 声明 state 的地方},reducers: { // 声明 reducer 函数的地方}
});
// 将 store 导出去。
const store = configureStore({reducer: userSlice.reducer
})export default store;
  • 第三步,在 src/index.js 中引入 store
import store from './store/index';
import { Provider } from 'react-redux'
root.render(<Provider store={store}><RouterProvider router={router} /></Provider>
);

解释:

  1. 从 store.js 中引入 store 对象。
  2. 从 redux 中引入 Provider 组件,并将原先的 RouterProvider 组件包裹起来。
  3. store 作为参数传递给 Provider 组件。

目前来讲,页面跟原来没有区别,但是现在我们多了 redux 的功能,何乐而不为呢。

1.4 什么是 Reducer

Reducer 与 Vuex 中的 mutations 差不多同一个意思,里面专门定义一些处理 state 的函数,reducer 主要接受一个 state 和一个 action ,根据这两个参数处理相关逻辑,然后返回新的 state (遵循前面所说的“不可变原则”)。

1.5 什么是 dispatch(action)

  • dispatch 是用来调用 reducer 函数的。
  • action 是 dispatch 调用 reducer 函数时要传递的一个描述对象,好让 reducer 知道该干什么事。该描述对象总共就俩参数:type/payload,type 是调用 reducer 的函数名,payload 是我们要传参的数据,给 reducer 接受用的。

1.6 使用

理解 reducer/dispatch/action 三大核心概念之后,我们来开始使用:

  • 第一步,在 initialState 对象中定义全局响应式数据。
// 省略...
initialState: {// 新增user: {name: 'Jack',desc: 'Hello,world!'}
},
  • 第二步,新建 User 组件,该组件用来访问上面声明的响应式数据,并挂载到 App 和 AppDetail 中渲染,代码如下:
// ./src/index.js
// ...省略
import { Provider, useSelector } from 'react-redux'
// 新增
const User = () => {// 用 redux 提供的钩子来获取 stateconst user = useSelector(state => state.user); return (<div><span>{user.name}</span><span> says: {user.desc}</span></div>)
}
// ./src/index.js
// ...省略
class App extends React.Component {// ...省略render() {return (<div><User /> // 新增:挂载 User<List /> </div>)}
}
const AppDetail = () => {return (<h1>Detail data<User /> // 新增:挂载 User</h1>)
}

现在的效果:Jack says: Hello,world!
请添加图片描述

  • 第三步,声明 reducer 函数来变更 state 数据。
// .src/store/index.js
// ...省略
reducers: { // 声明 reducer 函数的地方// 新增changeUserInfo(state, action) {const { payload } = action;switch(payload.state) {case 'name':return {...state,user: {...state.user,name: '杰克',}}case 'desc':return {...state,user: {...state.user,desc: '你好,世界!',}}default:return state;}}}

changeUserInfo 函数解释:

  1. 根据 payload.state 即我们准备传参的数据来变更用户名 name 还是描述
    desc
  2. 遵循不破坏 state 原则,这里我们用扩展符来合并。
  • 第四步,使用 dispatch(action) 来触发 reducer,完成变更效果。
// ./src/index.js
import { Provider, useSelector, useDispatch } from 'react-redux'
const User = () => {const user = useSelector(state => state.user);const dispatch = useDispatch(); // 引入触发 reducer 的钩子return (<div><span>{user.name}</span><span> says: {user.desc}</span>// 以下是新增的<button onClick={() => dispatch({type: 'User/changeUserInfo',payload: {state: 'name'}})}>更换名字</button><button onClick={() => dispatch({type: 'User/changeUserInfo',payload: {state: 'desc'}})}>更换描述</button></div>)
}

现在来看看总体效果:
React  用一个简单案例体验一遍 React-dom  React-router  React-redux 全家桶

五、总结

不知不觉,我们已经用到了 React-dom & React-router& React-redux:

root.render( // react-dom<Provider store={store}> // react-redux<RouterProvider router={router} /> // react-router-dom</Provider>
);

恭喜你已成功入门 React 全家桶,剩下就交给实践的时间来帮助我们熟能生巧。

有问题欢迎指出!

完!


案例已放在 github 上