前言

前些年学过的Vue似乎都不再出现在招聘简历里了,我观察到React在大型企业和项目实例中的广泛应用,于是决定在进行实习前系统学习并做出一个符合商业规范的工程化React项目。

React

React是一个前端框架,由Meta开源,其核心是数据驱动视图,即分离渲染和数据获取,以数据更新驱动视图更新,这样做当然有好处,后面会讲到。

常见属性和事件

事件绑定和参数传递

事件,事件通过on + 事件名,首字母大写,这里的事件是web预设的事件。

1
2
3
4
5
6
7
8
9
10
export default function Home() {
const clickHandler = () => {
alert("你点击了按钮!");
};
return (
<main>
<button onClick={clickHandler}>点击我</button>
</main>
);
}

传递事件参数e

1
2
3
4
5
6
7
8
9
10
export default function Home() {
const clickHandler = (e) => {
alert("你点击了按钮!",e);
};
return (
<main>
<button onClick={clickHandler}>点击我</button>
</main>
);
}

传递自定义实参

1
2
3
4
5
6
7
8
9
10
export default function Home() {
const clickHandler = (name) => {
alert("你点击了按钮!",name);
};
return (
<main>
<button onClick={() => clickHandler("jack")}>点击我</button>
</main>
);
}

两者并存则是

1
2
3
4
5
6
7
8
9
10
export default function Home() {
const clickHandler = (name,e) => {
alert("你点击了按钮!",name,e);
};
return (
<main>
<button onClick={(e) => clickHandler("jack",e)}>点击我</button>
</main>
);
}

组件自定义和使用

定义一个按钮组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Button() {
return (
<button onClick={clickHandler}>
点击我
</button>
);
}

//使用它。
export default function Home() {
return (
<main>
<Button></Button>
</main>
);
}

状态变量

通过useState向组件添加一个状态,藉由状态来控制组件的条件渲染。这其中,useState是一个函数,传入初始状态数值,返回一个数组,分别是状态变量和状态更新函数。需要注意的是一般的js变量赋值修改状态不一定影响界面渲染,而set函数修改的值会实时渲染到界面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const [count, setCount] = useState(0);

//使用示例
function Button() {
//使用usestate
const [count, setCount] = useState(0);
//使用set函数更新状态
const clickHandler = () => {
//倘若你直接用count++,count会自增但是不会驱动视图重新渲染。
setCount(count + 1);
};

return (
<button onClick={clickHandler}>{count}
</button>
);
}

状态可以由输入值指定的受控表单绑定,通过{value}绑定给表单,又由setValue传回事件e的输入,onChange负责实时更新渲染输入的内容。

1
2
3
4
5
6
7
8
const [value, setValue] = useState('');

<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>

React获取Dom元素

React的核心是数据驱动视图。而Dom是HTML中的真实元素,直接操作Dom可能导致网页重排,重绘,影响性能;且在未区分时可能导致由React+Dom的双向修改,这时网页不一定按照设计的真实需求走向,所以推荐一般情况下不直接获取并修改Dom(遇到数据无法驱动的视图时使用)。
获取Dom元素只需要使用useRef函数生成ref对象,并在对应的元素位置进行绑定,在Dom可用时(Dom需要渲染完毕才能使用)使用ref.current属性即可获取Dom对象。

1
2
3
4
5
const inputRef = useRef(null);

<input type="text" ref={inputRef} />

console.log(inputRef.current)

样式定义

对于样式处理,React主要通过内嵌样式或者引入样式,示例如下。

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
//内嵌样式
<div style={{color:'red'}}></div>
//一般来说样式过多时这样的写法难以直观阅读,所以推荐css样式和tsx分离

//同页定义
const style = {
color:'red',
}

<div style={{style}}></div>


//专用定义样式页,示例page.css
.button {
padding: 10px 20px;
color: white;
background: #1677ff;
border: none;
border-radius: 6px;
cursor: pointer;
}
//引入样式
import styles from "./page.css";

export default function Home() {
const [count, setCount] = useState(0);

return (
<main>
{/* 在classNam后面跟上样式 */}
<button
className={styles.button}
onClick={() => setCount(count + 1)}
>
点击次数:{count}
</button>
</main>
);
}

属性传递与Hook函数

不同组件间的数据传递需要专门的函数和属性。

父传子-属性传递

父组件元素传入子组件需要先将父组件的属性绑定到子组件然后用props属性传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Son (props) {
// props: 对象里面包含了父组件传递过来的所有的数据
console.log(props)
return <div>this is son,{props.name}</div>
}

export default function Home() {
const name = 'this is app name'
return (
<div>
{/* 属性绑定很重要 */}
<Son name={name} />
</div>
)
}

其中包含多种可以传值的类型,示例如下。但注意属性仅能传递不能修改,修改仅能由父组件修改。

1
2
3
4
5
6
7
8
9
<Son
name={appName}
age={20}
isTrue={false}
list={['Vue', 'React']}
obj={{ name: 'jack' }}
cb={() => console.log(123)}
child={<span>this is span child</span>}
/>

在子组件中嵌套的内容会以props中的children属性传入子组件,示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Son (props) {
// props.children会显示<span> this is span </span>
console.log(props.children)
return <div>this is son,{props.name}</div>
}

export default function Home() {
return (
<div>
{/* 属性绑定很重要 */}
<Son>
<span> this is span </span>
</Son>
</div>
)
}

子传父-属性传递

子组件属性传入父组件只需要将父组件方法传入子组件然后调用即可。

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
//这里的写法{ onGetSonMsg }是解构写法
//本质上const onGetSonMsg = props.onGetSonMsg;
//等价于const { onGetSonMsg } = props;同名属性可以解构和继承
function Son ({ onGetSonMsg }) {
// Son组件中的数据
const sonMsg = 'this is son msg'
return (
<div>
this is Son
<button onClick={() => onGetSonMsg()}>sendMsg</button>
</div>
)
}

function App () {
const getMsg = (msg) => {
console.log(msg)
}
return (
<div>
this is App
<Son onGetSonMsg={getMsg} />
</div>
)
}

export default App

同级-属性传递

一言以蔽之,兄弟组件的属性传递需要靠,子传父,父传子套娃式实现。称之为状态提升。

context跨层组件通信

例如A→B→C,则A对C的通信是跨层的。这时可以用到Context机制进行跨层通讯,具体来讲,使用createContext方法创建一个上下文对象。在顶层组件里调用对象的Provider方法来提供数据。在底层组件中使用useContext钩子函数获取数据。

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
// 先调用
import { createContext } from "react"
// 创建上下文对象
const MsgContext = createContext()

// 使用它传数据
function App () {
const msg = 'this is app msg'
return (
<div>
<MsgContext.Provider value={msg}>
this is App
<A />
</MsgContext.Provider>
</div>
)
}

// ...
// 然后使用它
function B () {
const msg = useContext(MsgContext)
return (
<div>
this is B compnent, {msg}
</div>
)
}

export default App

useEffect

useEffect是一个React Hook函数,用于创建由渲染附带的操作,如渲染页面后加载数据。

1
2
3
4
5
6
7
8
9
import { useEffect } from "react"

useEffect(() => {
//想做的操作
},[])
//空数组依赖仅在组件第一次渲染(挂载)完成后执行一次
//不传依赖时组件第一次渲染完成后 + 每次组件重新渲染(更新)完成后
//特定依耐项在第一次渲染完成后 + 当且仅当数组里的依赖项发生变化时
//数据驱动视图更新,这是react的核心

需要说明的是,由于React的数据驱动视图更新的特性,我们需要在渲染完成后再获取数据然后再渲染一次。这是为了区分页面渲染和数据获取(这类操作都称之为副作用,也就是除开页面渲染的操作),试想假如我们在页面渲染过程中发送请求,则数据回来时我们需要再渲染,则可能会多次发送请求,所以我们将页面渲染和副作用分离开,在页面渲染完成后使用useEffect调用。
而副作用在使用完成后需要清理,以下面为例,第一次渲染完成后打印1,而count变化时会打印2,然后再打印1,也就是重执行时先结束并清理useEffect的副作用运行,再重新调用,这就是React的副作用清理。这里的清理函数就是return,没有return就无法清理,而具体的清理操作如关闭计时器等需要自行设置。

1
2
3
4
5
6
7
useEffect(() => {
console.log('1. 副作用执行了(比如开启定时器)')

return () => {
console.log('2. 清理函数执行了(比如关闭定时器)')
}
}, [count])

自定义Hook函数

如上所示,React的内置Hook函数类型和作用包扩

  • useState:让函数组件拥有状态。
  • useEffect:让函数组件拥有处理副作用的能力(替代生命周期)。
  • useContext:让函数组件能读取 Context。
  • useRef、useMemo、useCallback 等等。

但Hook函数的使用需要遵循以下要求:

  1. 首先,只能在函数组件的最顶层调用 Hook。
  2. 其次,只能在 React 函数组件或自定义 Hook 中调用 Hook,且不能有条件限制。

这时,倘若内置的 Hook 不够用,或者你想把一堆复杂的逻辑(状态 + 副作用)打包复用,你就可以自己写一个函数。

自定义 Hook 的本质:
它就是一个普通的 JavaScript 函数,但是它的名字必须以 use 开头,并且它内部调用了其他的 Hook(内置的或自定义的)。

Redux

Redux集中状态管理工具,由前面的组件通信可以知道,当一个属性在全局不同位置被使用时,会出现通信复杂管理困难的问题,这里我们使用Redux管理独立于框架的全局属性,使得全局的每一个组件都可以获得属性并以特定方式修改它。

  • 定义一个 reducer 函数 (根据当前想要做的修改返回一个新的状态)
  • 使用createStore方法传入 reducer函数 生成一个store实例对象
  • 使用store实例的 subscribe方法 订阅数据的变化 (数据一旦变化,可以得到通知)
  • 使用store实例的 dispatch方法提交action对象 触发数据变化 (告诉reducer你想怎么改数据)
  • 使用store实例的 getState方法 获取最新的状态数据更新到视图中

在React中使用redux,官方要求安装俩个其他插件 - Redux Toolkit 和 react-redux。

ReactRouter

路由对前端来说,则是一个path路径对应一个组件component,当我们在浏览器访问一个path,对应该path的组件会在浏览器页面中渲染。

1
2
3
4
5
6
7
8
9
10
const routes = [
{
path: '/about',
component: About,
},
{
path: '/article',
component: Article,
},
]

通过npm i react-router安装react-router,可以使用路由功能。