0%

react基础学习

闭关学习 react!

核心概念

jsx

1
2
3
const name = "Josh Perez";
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(element, document.getElementById("root"));

在大括号中可以填入任何有效的 javascript 表达式。

在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。

也就是说,你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX:

React DOM 在渲染所有输入内容之前,默认会进行转义。这可以有效防止 XSS 攻击。

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。他会创建这样一个虚拟 dom 对象。

1
2
3
4
5
6
7
const element = {
type: "h1",
props: {
className: "greeting",
children: "Hello, world!",
},
};

元素渲染

React 元素是不可变对象,一旦被创建,就无法更改他的子元素或者属性。更新 UI 的唯一方式就是创建一个新的元素,将其传入 ReactDOM.render()。

React 只更新它需要更新的部分。React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。

组件&props

函数组件,接受 props,返回 jsx

1
2
3
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

组件可以是用户自定义的组件,这时它会将 jsx 所接收的属性以及自组件转换为单个对象传给组件。组件必须以大写字母开头。

1
2
3
4
5
6
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(element, document.getElementById("root"));

解构写法

1
ReactDOM.render(<Comment {...data}>,document.getElementById('root'));

props 应该是只读的,函数组件都应该为纯函数

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

当然,应用程序的 UI 是动态的,并会伴随着时间的推移而变化。在下一章节中,我们将介绍一种新的概念,称之为 “state”。在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。

组件的运作方式

  • render 发现一个用户自定义组件,如果标签名是大写字母开头的,就会认为它是一个用户自定义组件
  • 先把 jsx 属性封装为一个 props 对象
  • 把它作为参数传递给组件
  • render 方法会把次 react 元素渲染到页面上

state&生命周期

不要直接修改 state

1
2
// Wrong
this.state.comment = "Hello";

而应该使用 setState()

1
2
// Correct
this.setState({ comment: "Hello" });

state 更新可能是异步的

不要依赖他们的值来跟新下一个状态

1
2
3
4
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});

可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:

1
2
3
4
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment,
}));

state 的更新可以被合并

数据向下流动

state 是局部的,除了拥有并且设置了它的组件,其他组件都无法访问。组件可以选择把它的 state 作为 props 向下传递给它的子组件中

事件处理

react 事件命名采用小驼峰。

使用 jsx 语法时,需要传入一个函数作为事件处理函数,而不是一个字符串,不能使用 return false 的方式组织默认行为,必须显式使用 preventDefault。

传参的方式

默认会将 e 作为最后一个参数传递,如果使用箭头函数的方式绑定,需要使用如下形式

1
(e) => handleFunc(e);

条件渲染

if && || 三目运算符

阻止组件渲染

让 render 函数返回 null

列表&key

渲染多个组件

使用 map 数组方法,渲染列表时应该提供一个 key 属性,通常使用数据中的 id 来作为元素的 key。

表单

受控组件

在 HTML 中,表单元素(如 input、 textarea 和 select)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。

我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 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
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: "" };

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
this.setState({ value: event.target.value });
}

handleSubmit(event) {
alert("提交的名字: " + this.state.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="提交" />
</form>
);
}
}

textarea

在 html 中,textarea 使用子元素定义其文本,在 react 中,使用 value 属性代替

select

在 html 中 使用 selected 表明属性默认选中,react 在根 select 标签使用 value 属性

input type=file

在 react 中,它是一个非受控组件

处理多个输入

当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。

受控输入空值

在受控组件上指定 value 的 prop 会阻止用户更改输入。如果你指定了 value,但输入仍可编辑,则可能是你意外地将 value 设置为 undefined 或 null
如果确实是只读属性,应当加上 readOnly 属性

1
2
3
4
5
ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function () {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

状态提升

传递共同父组件的 props,props 是只读的,要在子组件中改变它,通常使用受控组件来解决,将父组件中改变 state 的函数作为 props 传递给子组件调用。

组合&继承

使用组合而非继承实现组件之间的代码重用

类似于 vue 的 slot,默认放在组件内部会作为 props.children 传递给子组件,当需要区分的时候,需要自定约定传入的 props

高级指引

无障碍

语义化

使用 React Fragments 来组合各个组件,类似于 Vue 中的 template
简写形式是 <></ >

其他内容看起来不是那么重要,略过

Context

react.createContext

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。

1
const MyContext = React.createContext(defaultValue);

Class.contextType

挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。不对其赋值是无法使用 this.context 获取值的。

refs 转发

refs 提供了一种方式,允许我们访问 DOM 节点或者在 render 方法中创建的 React 元素

基本用法 首先声明引用

1
const ref = React.createRef();

然后再你希望获取引用的节点上

1
<input type="text" ref={ref} value={props.content} />

这样在声明周期函数中,就可以使用 ref.current 获取到该节点的引用的

在高阶组件转发 refs

一般我们用高阶组件,会将所有 props 传递到其包裹的组件。但是 refs 不会传递,

1
2
3
render() {
return <WrappedComponent {...this.props} />;
}

可以使用 React.forwardRef API 明确地将 refs 转发到内部的组件。React.forwardRef 接受一个渲染函数,其接收 props 和 ref 参数并返回一个 React 节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log("old props:", prevProps);
console.log("new props:", this.props);
}

render() {
const { forwardedRef, ...rest } = this.props;

// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <Component ref={forwardedRef} {...rest} />;
}
}

// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
// 然后它就可以被挂载到被 LogProps 包裹的子组件上。
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}

如果不使用 React.forwardRef,普通的函数组件是不会获得该参数的,因为函数组件没有实例。
不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:
使用 hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);

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

return (
<div>
<input type="text" ref={textInput} />
<input type="button" value="Focus the text input" onClick={handleClick} />
</div>
);
}

现在有个问题,useRef 和 createRef 看起来没啥区别啊

百度之后得到的答案是 createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。

useRef 仅能用在 FunctionComponent,createRef 仅能用在 ClassComponent。!!!

错误边界

如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。

珠峰前端

生命周期详解

初始渲染阶段

componentWillMount -> render -> componentDidMount

更新阶段

shouldComponentUpdate

  • 返回 true -> componentWillUpdate -> render -> componentDidUpdate
  • 返回 false 不处理

从父组件传来的 props 改变
componentWillReceiveProps

  • 返回 true -> shouldComponentUpdate -> 走上面的流程
  • 返回 false 不处理

销毁阶段

componentWillUnmount
在这个生命周期勾子中清除定时器,防止内存泄漏

生命周期

react16.3 生命周期的变化

我看的这个视频时 18 年的,在之后 react 的生命周期有一次大的变动,增加了 getDerivedStateFromProps 和 getSnapshotBeforeUpdate 这两个生命周期函数,同时 componentWillMount 和 componentWillReceiveProps 已经不推荐使用,将在未来的 react 版本中移除

getDerivedStateFromProps

这个生命周期会在每次 re-rendering 之前被调用
意味着即使你的 props 没有任何变化,而是父 state 发生了变化,导致子组件发生了 re-render,这个生命周期函数依然会被调用

生命周期新

PropTypes 类型检查

  • array 数组
  • bool 布尔值
  • func 函数
  • number 数字
  • object 对象
  • string 字符换
  • symbol 符号
  • node 任何节点 numbers strings elements 或者包含这些类型的数组或者片段
  • element React 元素
  • instanceOf(Message) 类的一个实例
  • oneOf([‘News’,’Photos’]) 枚举值
  • oneOfType([PropTypes.string,PropTypes.number]) 多种类型中其中之一
  • arrayOf(PropTypes.number) 某种类型的数组
  • objectOf(PropTypes.number) 某种类型的对象
  • shape 特定形式的对象
  • .required 必填
  • function(){return new Error()} 自定义验证器

性能分析

分析性能 react 可以直接使用 react_perf,然后使用 chrome 进行录制分析

避免重复渲染

重写 shouldComponentUpdate 减少重新渲染

PureConponent

继承自 component,它做了一个优化,它重写了 shouleComponentUpdate 方法
这个比较是浅比较,所以 props 如果是对象,会出现无法更新的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
shouldComponentUpdate(nextProps,nextState){
for(let prop in nextProps){
if(nextProps[prop]!==this.props[prop]){
return true;
}
}
for(let state in nextStates){
if(nextStates[state]!==this.states[state]){
return true;
}
}
return false;
}

immutable.js

可共享的变化是万恶之源,js 中的对象一般是可变的,因为使用引用赋值,对引用对象的修改会影响的原始对相关,为了避免这种影响,一般采用浅拷贝或者深拷贝的方式,但是如果对象的属性很多,就会造成 CPU 和内存的浪费。

immutable 中的数据一旦被创建,就不能被更改,所有修改和添加删除操作都会返回一个新的 immutable 对象。immutable 的实现原理是 Persistent Data Structure(持久化数据结构),这个咱之前也接触过,主席树的原理啊,抽空研究一下。

1
2
3
4
5
6
7
let { Map } = require("immutable");
let m1 = Map({ a: 1, b: 2, c: 3 });
console.log(m1.get("a"));
let m2 = m1.set("a", "11");
console.log(m2.get("a"));
console.log(m1.get("a"));
console.log(m1 === m2);

缺陷 体积过大 20k

seamiess-immutable

使用 Object.defineProperty 扩展的 Javascript 的 Array 和 Object 对象来实现,只支持 Array 和 Objcet 两种数据类型

hooks

useState

标准写法,参数就是 state 的初始值,解构得到的第二个变量是设置该 state 的函数

1
const [age, setAge] = useState(42);

useEffect

它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。

副作用函数还可以通过返回一个函数来指定如何“清除”副作用。例如,在下面的组件中使用副作用函数来订阅好友的在线状态,并通过取消订阅来进行清除操作:

使用规则

只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中。)

通过跳过 Effect 进行性能优化

1
2
3
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

createPortal

将组件渲染到组件树之外的地方,可以用来做弹窗

diff 算法处理 key

// TODO
希望操作最小化

第一步,把老数组在新数组中没有的元素移除掉

React Fiber

React render

触发 render 的场景

  • 状态更新时,类组件调用 setState 或者 函数组件的 state 更新
  • 父容器重新渲染,无论子组件的实现如何,都会重新渲染

性能优化

谨慎分配 state,为了避免不必要的 render,可以将状态下放到更低层级的组件

合并状态更新

使用 PureComponent 和 React.memo 避免不必要的 render 调用

避免组件不必要的渲染的方法有:React.memo 包裹的函数式组件,继承自 React.PureComponent 的 class 组件。

浅比较

用 PureComponent 或者 React.memo 修饰的组件,每次更新的时候都对 props 和 state 进行一次浅比较。如果有对象,且对象引用变化,就会触发重渲染。由于函数也是引用类型,如果传递的是箭头函数(匿名函数),组件也会在父组件重新渲染的时候重新渲染。

useMemo

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
function Example(props) {
const [count, setCount] = useState(0);
const [foo] = useState("foo");

const main = useMemo(
() => (
<div>
<Item key={1} x={1} foo={foo} />
<Item key={2} x={2} foo={foo} />
<Item key={3} x={3} foo={foo} />
<Item key={4} x={4} foo={foo} />
<Item key={5} x={5} foo={foo} />
</div>
),
[foo]
);

return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>setCount</button>
{main}
</div>
);
}