React 16.6.0 引入了lazy
和Suspense
。React.lazy
函数可以渲染一个动态的import作为一个组件。Suspense
悬停组件,它会在内容还在加载的时候先渲染fallback
。它们组合就能实现之前主要是使用loadable-components,来异步加载组件,Code-Splitting。
import React, {lazy, Suspense} from 'react';
const OtherComponent = lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}
可以查看React lazy Demo
需要注意的是:
React.lazy
函数只支持动态default
组件导入"plugins": ["@babel/plugin-syntax-dynamic-import"]
Suspense
目前只支持Code-Splitting, 数据异步获取的支持需要到2019年中……React.memo
基本就是React为函数组件提供的PrueComponent
或者shouldComponentUpdate
功能。下面的例子:
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
});
React 16.3 正式引入了Context API, 来方便跨组件共享数据,基本使用方式,按照官方例子:
const ThemeContext = React.createContext('light');
class ThemeProvider extends React.Component {
state = {theme: 'light'};
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
class ThemedButton extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{theme => <Button theme={theme} />}
</ThemeContext.Consumer>
);
}
}
可以发现消费组件需要按照函数的方式来调用,很不方便,因此新的语法可以赋值给class组件的静态属性contextType
,以此能够在各个生命周期函数中得到this.context
:
class MyClass extends React.Component {
static contextType = MyContext;
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
React 在版本16.8中发布了Hooks,可以在函数式组件中使用state和其他的React 功能。
React官方文档Introducing Hooks – React花了8个章节来讲述Hooks😬,一定要读一读,本文不会那么详尽,只是试图做一些融汇和贯通。
React从发布以来就是以单项数据流、搭积木的书写方式迅速流行,然后为了解决日益复杂的业务:
更进一步来说,Class组件this
加上生命周期函数的方式,难写,难读,易出错,而且AOT,树摇,Component Folding等先进的编译优化手段效果不好……
因此实际上Hooks就是为函数式组件赋能,以此来优化上述问题。
useState
的语法可能略微奇怪,但是却异常好用.
const [state, setState] = useState(initialState);
this.state
,useState
可以多次使用this.state
会自动合并对象,useState
不会useState
的中setState
直接传值,同样也可以传一个函数,以此在函数中获取到上次的stateuseState
的初始值如果需要一个耗时函数计算时候,给useState
传入函数,这样只会在初次调用。useState
,请不要在循环、条件或者嵌套函数中调用useState
,其实所有的Hooks你应该只在函数的顶层调用Demo react-useState - CodeSandbox
可以在useEffect
里面做一些,获取,订阅数据,DOM等“副作用”,它也可以实现于Class Component中的componentDidMount
, componentDidUpdate
和 componentWillUnmount
的调用,使用类似官方的例子:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
一个函数内就搞定了,componentDidMount
—> componentDidUpdate
—>componentWillUnmount
(注意Effect函数返回的函数),易读,精简。
记住,Hooks就是一些常规的JavaScript函数,只是约定以use
开头命名(方便阅读和Eslint)。因此Hooks自然就可以按照函数一样组合使用。
实际上这才是React Hooks真正释放想象,提高生产力的地方。
import { useEffect, useState } from 'react';
const useWindowSize = () => {
const [state, setState] = useState<{ width: number; height: number }>({
width: window.innerWidth ,
height: window.innerHeight,
});
useEffect(() => {
const handler = () => {
setState({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
return state;
};
更多的自定义Hooks可以查看: GitHub - streamich/react-use: React Hooks — 👍
在你高兴太早之前,useEffect
还有可选的第二个参数,可以穿入一个useEffect
内函数所依赖值的数组。
实际上所有的故事,所有的纠结都发生在这个参数😱。
useEffect
默认会在每次渲染后调用,如果你传传入一个[]
,效果就和componentDidMount
类似。
import { EffectCallback, useEffect } from 'react';
const useMount = (effect: EffectCallback) => {
useEffect(effect, []);
};
自然类似componentWillUnmount
可以:
const useUnmount = (fn: () => void | undefined) => {
useEffect(() => fn, []);
};
不过Hook也没有覆盖所有的生命周期,getSnapshotBeforeUpdate
和componentDidCatch
暂时没有对应的Hook。
来看如下的代码
const FunName = () => {
const [name, setName] = useState("init name");
function log() {
setTimeout(() => {
console.log("FunName after 3000 ", name);
}, 3000);
}
return (
<div>
<h2>Fun name log</h2>
<input value={name} onChange={e => setName(e.target.value)} />
<button onClick={log}>delay console.log</button>
</div>
);
};
class ClassNameView extends React.Component {
state = { name: "init name" };
log = () => {
setTimeout(() => {
console.log("ClassName after 3000 ", this.state.name);
}, 3000);
};
render() {
return (
<div>
<h2>class name log</h2>
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
/>
<button onClick={this.log}>delay console.log</button>
</div>
);
}
}
两个功能一样的组件,一个函数组件,一个Class组件,在按钮点击后3000ms之内两者的行为却不一样。
类似同样的组件,使用父组件的props
const FunName = () => {
function log() {
setTimeout(() => {
console.log("FunName after 3000 ", name);
}, 3000);
}
return (
<div>
<h2>Fun name log</h2>
<button onClick={log}>delay console.log</button>
</div>
);
};
class ClassNameView extends React.Component {
log = () => {
setTimeout(() => {
console.log("ClassName after 3000 ", this.state.name);
}, 3000);
};
render() {
return (
<div>
<h2>class name log</h2>
<button onClick={this.log}>delay console.log</button>
</div>
);
}
}
// 父组件
function App() {
const [name, setName] = useState("init name");
return (
<div className="App">
<h1>Hooks Capture props</h1>
<input value={name} onChange={e => setName(e.target.value)} />
<FunName name={name} />
<ClassNameView name={name} />
</div>
);
}
同样行为不一样
普通javascript函数也有如下的行为:
function sayHi(person) {
const name = person.name;
setTimeout(() => {
alert('Hello, ' + name);
}, 3000);
}
let someone = {name: 'Dan'};
sayHi(someone);
someone = {name: 'Yuzhi'};
sayHi(someone);
someone = {name: 'Dominic'};
sayHi(someone);
//执行结果:
//Hello, Dan
//Hello, Yuzhi
//Hello, Dominic
也就是函数组件的行为才是“正确的”行为,而Class组件行为的原因在于React会修改,this.state
和this.props
使其指向最新的状态。
useRef
一般用作获取DOM的引用,根据官方文档:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
可变的对象会存在于组件的整个生命周期,因此可以用来保存值,保证拿到最新的值。
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<h1>
Now: {count}, before: {prevCount}
</h1>
);
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
一般而言你需要将effects所依赖的内部state或者props都列入useEffect
第二个参数,不多不少的告诉React 如何去对比Effects, 这样你的组件才会按预期渲染。
当然日常书写难免遗漏,这个ESlint 插件的exhaustive-deps
规则可以辅助你做这些事情。
这里不再展开说,但是从我日常项目来看,这点还是需要费些心思的。
useCallback
会根据传入的第二个参数来“记住”函数。 可以用它来避免函数被作为callback传入子组件时不必要渲染。
而且函数组件内的函数,如果需要在被不同的生命周期中调用,最好使用useCallback
来处理,这样一方面拿到正确的值,一方面保证性能的优化。
function SearchResults() {
const [query, setQuery] = useState('react');
const getFetchUrl = useCallback(() => {
return 'https://siet.com/search?query=' + query;
}, [query]);
useEffect(() => {
const url = getFetchUrl();
// ... Fetch data and do something ...
}, [getFetchUrl]);
}
总结来说Hooks的:
可以更快速让大家写出,稳健,易测试,更易读的代码,enjoy~~
如果说Hooks改变了开发者如何写业务代码,那么Fiber就是React改变了如何渲染。简单来说,就是React 将任务切片,分优先级,然后按照一定策略来调度渲染,一举改变之前递归,不可打断式渲染。
更详尽的分析,等我搞懂了,再来说道~~~