React 重读- 高阶用法
核心概念
正确使用 state
- 不要直接修改 state, 而是使用 setState() 的方式去更新,否则组件不会重新渲染;
- state 的更新可能是异步的,所以不要依赖他们的值来更新下一个状态。可以让 setState() 接受一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 作为第二个参数:
this.setState((state, props) => {
return {
counter: state.counter + props.increment
}
})
- state 的更新会被合并,可以分别调用 setState() 来单独更新它们。
事件处理
-
在 react 中,不能通过返回 false 的方式阻止默认行为,必须显式的使用 preventDefault
handleClick(e) { e.preventDefault() console.log("The link was clicked.) }
-
绑定 this 的几种方式
- 在 constructor 中绑定 this
constructor(props) { super(props); this.state = {}; this.handleClick = this.handleClick.bind(this) }
- 在事件调用的使用绑定 this
<Button onClick={this.handleClick.bind(this)} />
- 使用箭头函数
handleClick = () => {}
- 使用 lodash-decorators 的 Bind 方法
import { Bind } from 'lodash-decorators' ... @Bind() handleClick() {}
-
向事件处理程序传参
// 这两种方式时等价的 <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
-
阻止组件渲染
让 render 方法直接返回 null, 则组件就不会有任何渲染。在组件的 render 方法中返回 null 并不会影响组件的生命周期。
受控组件
state 是"唯一数据源",并且只能通过 setState() 来更新。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做受控组件
状态提升
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的状态提升。
高级指引
Context
Context 设计目的是为了共享那些对于一个组件树而言是"全局"的数据,例如当前认证的用户、主题或首选语言。
如果只是想要避免层层传递一些属性,组件组合(component composition)有时候是一个 context 更好的解决方案。
const MyContext = React.createContext(defaultValue)
<MyContext.Provider value={/* 某个值 */} />
将 undefined 传递给 Provider 的 value 时,组件的 defaultValue 不会生效。
当 Provider 的 value 值发生变化时,它内部的组件都会重新渲染,并且不受制于 shouldComponentUpdate 函数。
context 会使用参考标识(reference identity)来决定何时进行渲染,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中出发以外的渲染。例如,当每一次 Provider 重渲染时,以下的代码会重渲染所有下面的 consumer 组件,因为 value 属性总是被赋值为新的对象:
class App extends React.Component {
render() {
return (
<Provider value={{ something: 'something }}>
<Toolbar />
</Provider>
)
}
}
为了防止这种情况,将 value 状态提升到父节点的 state里:
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
value: { something: 'something },
}
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
)
}
}
一个组件消费多个 context
function Content(theme, user) {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{User => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
)
}
错误边界
部分 UI 组件内的 JavaScript 错误会导致 React 的内部状态被破坏,并且在下一次渲染时产生可能无法追踪的错误,甚至导致整个应用崩溃。
为了这个问题, React 引入了错误边界——一种React组件, 这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。
错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
错误边界无法捕获的错误
- 事件处理
- 异步代码
- 服务端渲染
- 它自身抛出来的错误(并非它的子组件)
错误边界使用
当组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,就会编程一个错误边界。使用static getDerivedStateFromError() 渲染备用 UI,使用 componentDidCatch() 打印错误信息。
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = {
hasError: false
};
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, info) {
console.log(error, info);
}
render() {
if (this.state.hasError) {
// 自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
<ErrorBoundary>
<MyWidget>
</ErrorBoundary>
错误边界得只针对 React 组件,只有 class 组件才可以成为错误边界组件。错误边界尽可以捕获其子组件得错误,无法捕获自身得错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScript 中 catch {} 的工作机制。
Refs 转发
Ref 转发是一项将 ref 自动地通过组件传递给其一子组件的技巧。
Ref转发是一个可选特性,其允许某些组件接受 ref,并将其向下传递(换句话说,"转发"它)给子组件。
Refs 使用
- 通过调用 React.createRef 创建一个 React ref 并将其赋值给 ref 变量。
- React 传递 ref 给 forwardRef 内函数 (props, ref) => ...., 作为其第二个参数。
- 当 ref 挂载完成,ref.current 将指向 <button> DOM 节点
const FancyButton = React.forwardRef((props, ref) => {
console.log("props", props);
console.log("ref", ref);
return <button ref={ref}>{props.children}</button>;
});
// 你可以直接获取 DOM button 的 ref
const ref = React.createRef();
export default function MyDemo () {
// return <FancyButton ref={ref}>Click me!</FancyButton>;
return <FancyButton ref={ref} children="Click me!" />;
};
高阶组件(HOC)中转发 refs
如果你对 HOC 添加 ref,该 ref 将引用最外层的容器组件,而不是被包裹的组件
const FancyButton = React.forwardRef((props, ref) => {
return <button ref={ref}>{props.children}</button>;
});
// 你可以直接获取 DOM button 的 ref
const ref = React.createRef();
function MyDemo () {
// return <FancyButton ref={ref}>Click me!</FancyButton>;
return <FancyButton ref={ref} children="Click me!" />;
};
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console("old props", prevProps)
console("new props", this.props)
}
render() {
const { forwardRef, ...rest } = this.props
return <Component ref={forwardRef} {...rest} />
}
}
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardRef={ref} />
})
}
export default logProps(MyDemo)
DevTool 中显示自定义名称
<LogProps {...props} forwardedRef={ref} />
- forwardRef.displayName =
name
;
Fragments
使用显式 <React.Fragment> 语法声明的片段可能具有 key,key 是唯一可以传递给 Fragment 的属性
高阶组件(HOC)
- 高阶组件是参数为组件,返回值为新组件的函数。它是一种基于 React 的组合特性二型策划给你的设计模式。
- 组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
- Hoc是纯函数,没有副作用。它可以与其他 HOC 组合,甚至可以与其自身组合。
-
可以将 HOC 视为可参数化容器组件。容器组件担任分离将高层和底层关注的责任,由容器管理订阅和状态,并将 props 传递给处理渲染 UI。HOC 使用容器作为其实现的一部分。
容器组件和展示组件
约定
-
不要在 HOC 中修改组件原型, 或以其他形式修改它。而应该使用组合的方式,通过将组建包装在容器组件实现功能。
-
HOC 为组件添加特性,自身不应该大幅改变约定,HOC 返回的组件与原组件应保持类似的接口。
-
最大化可组合性
-
包装显示名称以便轻松调试
Render Props
render props 是指一种在 React 组件之间使用一个值为函数的 props 共享代码的技术。具有 render pros的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑。
class Cat extends Component {
render() {
const mouse = this.props.mouse
return (
<img
src="image.png"
style={{ position: 'absolute', left: mouse.x, top: mouse.y, width: '100px' }} />
)
}
}
class Mouse extends Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: "100%" }} onMouseOver={this.handleMouseMove}>
{ this.props.render(this.state) }
</div>
);
}
}
export default class MouseTracker extends Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<Mouse render={mouse=> (
<Cat mouse={mouse} />
)} />
</div>
)
}
}
网友评论