Context
用于跨层的组件通信,类似Vue的provider+inject,挂载于根React结点可作为全局状态管理就像Vuex的实现。
API
- React.createContext(defalultValue):创建一个一个Context对象。
- 当一个订阅了这个Context的组件被渲染,会寻找离他最近的一个provider中读取当前的context值。
- 只有当前组件所处树种没有找到匹配的provider,defaultValue参数会生效。这有助于单元测试。
- Context.Provider():每个Context.provider对象会返回一个Provider React组件,其允许子集订阅者订阅他的变化。
<MyContext.Provider value={/* 某个值 */}>- 接收一个value属性,这个值被传递给订阅者。当value发生变化,订阅者组件会重新渲染且不受钩子函数阻止的影响。
- 使用Object.is的判断是否改变逻辑
- Context.Consumer:在这个组件中,可以使用一个参数为value的参数来返回要返回的JSX。
- 返回的React节点对应的value参数等同于组件树上最近provider提供的值或者找不到时的默认值。
- 在Context中保存几个方法用于改变值,像Vuex中的mountion一样为组件修改它的值提供可以调用的函数。
官网的例子
- 使用
- 创建一个Context对象
- 将此Context在你需要的地方improt,并将其注入对应的组件(与Vue的eventBus相同/Vuex中你无需自己进行这一步)
- 在你的JSX渲染中使用你注入的Context
- 使用时,你引入的context对象会寻找provider,没有则会使用创建时的默认值
- 这是使用了Context的组件
import {ThemeContext} from './theme-context';
class ThemedButton extends React.Component {
render() {
let props = this.props;
let theme = this.context;
return (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
);
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;
- 以下内容使用了两次上述组件,被provider包裹的一个和使用了默认值的一个。
import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';
// 一个使用 ThemedButton 的中间组件
function Toolbar(props) {
return (
<ThemedButton onClick={props.changeTheme}>
Change Theme
</ThemedButton>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
};
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
}
render() {
// 在 ThemeProvider 内部的 ThemedButton 按钮组件使用 state 中的 theme 值,
// 而外部的组件使用默认的 theme 值
return (
<Page>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
<Section>
<ThemedButton />
</Section>
</Page>
);
}
}
ReactDOM.render(<App />, document.root);
性能陷阱
- Context会使用id来决定何时更新渲染
- 当provider的父组件重新渲染,value值可能会被重新赋值(例如value为一个固定字符串)
- 为了防止这种情况可以将value值提升到发生重新渲染父组件的state上
高阶组件
- 是以参数为组件,返回新组件的函数
- 用于提取组件之间的逻辑关系的共同部分,来提取出这一部分逻辑对应的公共代码。
- 定义一个函数,参数为逻辑关系的一方(通常为子组件),返回逻辑关系的另一方的React组件class。其中部分包含了这种逻辑关系的共同部分,以及根据其他参数确定的其他特别的逻辑或取值。
- 使用这个函数去生成逻辑关系的另一方,自此就实现了对共同逻辑的提取。
- tips:
- 你不应该在HOC中更改传入的组件!
- HOC不应该改变使用HOC之前的约定逻辑。所以你应该对props做过滤,避免一些在实现共用的过程中生成的冗余props内容。但是除了你在HOC过程中用到的或者确定的冗余props外,其他的内容都应被完整的传递给子组件。
- 你也可以对对高阶组件再进行一次抽象提取,来形成一个返回高阶函数的高阶函数,这样组件的双方都是可定义的而非static。
- 约定:为了便于调试,HOC生成的组件名字应带有对应HOC名前缀或者被其包裹。
- 陷阱-render的绝对刷新:你不应该使用HOC来操作render部分,这会导致render每次调用都会用HOC计算全新的内容,导致diff认为他是全新的树,除了性能问题外,这更会会导致组件重新挂载时丢失该组件和所有子组件状态的丢失。
- 陷阱-静态方法的丢失:HOC应用在组件时,增强函数像闭包一样保存了最初组件状态,当你为组件添加其他的静态方法时,HOC生成的组件并无法获取添加的静态方法,你需要再HOC中手动拷贝或使用
hoist-non-react-statics库拷贝,或者不要把方法挂载在组件上而是单独提取并按需引入这个方法。
JSX
JSX实际是React.createElement(component, props, ...children)的语法糖
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
以上代码会被bable编译为下面的形式
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
tips
- 因为JSX本质是React的语法糖,所以你必须在使用JSX时引入React以及你在JSX中用到的组件
- 你可以在JSX的Tag位置使用点语法,来从一个对象中取出对应的组件。
- 你仍然需要保证以大写字母开头
- 你可以使用点语法但不可以使用表达式计算值,比如<arr[1] />是无效的
- 大小写:JSX的tag为小写时,编译时会把这个值作为一个HTML原生组件名,将其作为静态字符传入createElement,当首字母为大写,则传入的是变量名而非字符串。
- 为了做到运行时选择tag类型,或者必须要使用小写开头的组件,建议在render之前定义新的变量来对这些计算值或小写值进行一次映射。
- props的默认值为true,这与HTML标签属性行为一致,但不建议简写因为容易造成与JS中ES对象简写默认值为他本身的写法混淆。
- 你可以使用{...props}来在JSX中渐变的一次性传入多个props。
- 你可以在JSX中使用标签包裹其他部分,这部分被包裹的内容会作为props.children传入这个组件
错误边界
- 部分UI的JS错误不应该导致整个应用的崩溃。React16引入了新的概念—错误边界。
- 这种组件可以捕获&打印发生在子组件树任何位置的JS错误并渲染出备用的UI。
- 事件处理、异步代码、服务器渲染、本组件自身错误并不算在内
- 当一个组件定义了static的
getDerivedStateFromError或componentDidCatch的任意一个生命周期钩子,那么他就会变成一个错误边界。- 抛出错误后可以使用
getDerivedStateFromError()渲染备用UI或componentDidCatch()打印错误信息
- 抛出错误后可以使用
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Uncaught Errors
从React16开始,任何未被错误边界捕获的错误会导致整个React组件树被卸载。
事件处理器
- 错误边界无法捕获事件处理器内部错误
- 事件处理器不会在渲染期间触发,即使他们抛出异常,React仍然知道需要展示什么。
- 捕获事件处理器的错误,仍然使用普通的
tyt\catch
Others/API
firwardRef——Ref转发
- 解决如何让父组件获得子组件ref的问题
- 使用
- 在父组件使用
React.createRef创建一个React Ref变量 - 将这个变量作为props,name为
ref传递给子组件 - 使用React.forwardRef返回子组件,传入一个参数是(props,ref)的函数,返回render。
- 将参数ref绑定在JSX中的DOM上。
- 当子组件挂在完成后,这个变量的.current指向绑定的dom节点
- 在父组件使用