一种可以在组件之间共享值的方式,不必显示通过组件树逐层传递props。
使用React.createContext创建一个context,再使用Provider将值传递给子组件,在子组件中指定contextType=创建的context,React 会往上找到最近的 theme Provider,使用时通过this.context即可获取值。
组件复用性变差。
传递对象为对象时,检测变化的方式会导致,每一次Privider重新渲染时,下面的组件都会重新渲染,因为value属性总被赋值新的对象,所以应该将value状态提升到父节点的state里。
比如只有最深的子组件需要知道最顶部组件的参数
1.组件组合:包含关系(chidren prop、jsx prop)、特例关系(把组件看作是其他组件的特殊实例),将最深的子组件自身传递下去,中间组件无需知道props,但是提升到最高层处理会使得高层组件变得更复杂,强行将底层组件适应这样的形式。
2.context:向下广播数据,所有组件都可以访问到数据并能访问到数据的更新,不受限于shouldComponentUpdate函数。
// 只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效
const ThemeContext = React.createContext({ theme: 'light' });
class App extends React.Component {
render() {
return (
// value属性为固定写法
// 另一种方式:函数作为子元素,接收当前的context值,返回React节点
{
value => {value.theme}
}
);
}
}
function Toolbar() {
return (
);
}
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return ;
}
}
一种将子节点渲染到父组件以外的DOM节点的方式。
在组件的render中return ReactDOM.createPortals(child, container),参数同ReactDOM.render,可以用于视觉上弹出的容器,如对话框、提示框等。
虽然可以渲染到不同的节点,一个从portal内部会触发的事件会一直冒泡至React树的祖先。
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
高阶组件(HOC:higher order component)就是一个函数,接受一个组件作为参数,并返回一个新的组件。高阶组件不会修改原组件,也不会继承复制原组件的行为,相反,高阶组件是通过将原组件包裹在容器组件里的方式来组合使用原组件,是一个没有副作用的纯函数。
1.大部分实现逻辑相同的组件抽象成一个模式。
2.在组件中新增需求,可以通过反向继承去继承之前的组件。
例子:工作台的简单查询页流程大致相同,进入页面请求默认数据,输入查询条件请求、分页功能,可以将这些流程通过高阶函数统一化。
1.属性代理(props proxy):可以操作props,做自定义操作;抽离state,通过{props,cb}传递给WrappedComponent,再通过回调函数获取state,也可以运用在处理表单的时候,在高阶函数获取到表单信息之后再去做其他的操作。
const connect = key => Com => {
return class connectComponent extends React.Component {
state = {
[key]: store[key]
}
}
componentDidMount() {
let that = this;
window.store = new Proxy(store, {
get: (target, key, receiver) => {
return Reflect.get(target, key, receiver);
},
set: (target, key, value, receiver) => {
that.setState({
[key]: value
})
return Reflect.set(target, key, value, receiver)
}
})
}
render() {
return
}
}
let store = {
name: 'myName'
}
@connect('age') // 相当于User = connect(User)('age')
class User extends React.Component {
render() {
return {this.props.age}
}
}
2.反向继承(inheritance inversion):通过去继承WrappedComponent,得到生命周期、state、function,也可以对WrappedComponent进行渲染劫持(hijack),控制他的render函数。
const loading = Com => {
showLoading() {}
hideLoading() {}
return class LoadingComponent extends Com {
// 不建议写render,会让组件越来越深,样式也可能错乱
render() {
return
loading...
{super.render()}
}
}
}
@loading
class User extends React.Component {
componentDidMount() {
this.showLoading();
}
render() {
return user
}
}
1.不要在render函数中使用高阶函数:render函数返回组件树有差异会更新,如果不相等会完全卸载旧的子对象树,重新加载一个组件会引起原有组件的所有状态和子组件的丢失,如果每次返回的结果都不是一个引用,react认为发生了变化。在组件定义外使用高阶函数,可以试新组件只被定义一次,在渲染的过程中包保证都是同一个组件。
2.必须将原组件的静态方法拷贝给新组件:因为用高阶组件包装组件时,原组件会丢失所有静态方法,可以使用hoist-non-react-statics来自动拷贝。
3.Refs属性不能传递,因为refs是一个伪属性,高阶组件创建的组件元素添加ref,指向的是最外层容器组件实例,而不是包裹组件,16.3版本添加了React.forwardRef来解决这个问题。
4. 高阶组件不会修改子组件,也不拷贝子组件的行为。
5.给HOC添加class名,方便调试。
使用render作为属性传入组件,可以共享状态、封装组件行为等,动态决定需要渲染的组件。
多个组件都需要重用追踪鼠标位置,但是作用不同,可以将渲染组件传入行为组件的render属性中,为行为组件提供需要的参数。
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
);
}
}
class Mouse extends React.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 (
{this.props.render(this.state)}
);
}
}
class MouseTracker extends React.Component {
render() {
return (
Move the mouse around!
(
)}/>
);
}
}