文章目录
- [1. Portals将子组件渲染到父组件以外的DOM节点上](#1. Portals将子组件渲染到父组件以外的DOM节点上)
- [2. Context组件树中传递数据](#2. Context组件树中传递数据)
- [3. react中如何加载异步组件](#3. react中如何加载异步组件)
- [4. shouldComponentUpdate有什么用](#4. shouldComponentUpdate有什么用)
- [5. state中值的不可变性](#5. state中值的不可变性)
- [6. HOC和Render Props代码复用和逻辑分离](#6. HOC和Render Props代码复用和逻辑分离)
- [7. redux](#7. redux)
- 8.React-Redux
- [9. react-reducx异步action](#9. react-reducx异步action)
1. Portals将子组件渲染到父组件以外的DOM节点上
在React中,Portals是一种机制,可以将子组件渲染到父组件以外的DOM节点上。通常情况下,React组件会被渲染到它们的父组件的DOM结构中,但有时我们希望将某个组件渲染到DOM结构的其他位置或根节点上,这时就可以使用Portals。
Portals的主要用途包括但不限于以下几点:
- 在应用中的任意位置渲染组件,无需将所有组件都嵌套在同一个层级结构中。
- 在模态框或弹出窗口等场景下,将组件渲染到最顶层的DOM节点上,确保它们处于最前面且不受其他布局影响。
- 在复杂布局下,将组件渲染到特定的DOM节点上,实现更灵活的布局控制。
举个例子,假设我们有一个模态框组件Modal,并希望将其渲染到页面的最顶层节点上,可以使用Portals实现:
js
import { createPortal } from 'react-dom';
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
render() {
return createPortal(
this.props.children,
modalRoot
);
}
}
// 在父组件中使用Modal组件
class App extends React.Component {
render() {
return (
<div>
<h1>Hello, React!</h1>
<Modal>
<div>
This is a modal content.
</div>
</Modal>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
在上面的例子中,Modal组件使用Portals将其子组件渲染到了id为'modal-root'的DOM节点上,而不是直接嵌套在App组件内部。这样就实现了在页面的不同位置渲染组件的效果。
2. Context组件树中传递数据
React中的Context是一种全局状态管理的方法,它允许在组件树中传递数据,而不必一层层地手动传递props。通常用于在组件之间共享一些全局的信息。
Context包括两部分:Provider和Consumer。Provider提供数据并传递给子组件,而Consumer消费Provider提供的数据。
一个简单的例子是,在一个多层嵌套的组件结构中,如果需要在底层组件访问或修改顶层组件的状态数据,使用Context就会很方便。
js
// 创建一个Context
const MyContext = React.createContext();
// 顶层组件提供数据
class ParentComponent extends React.Component {
state = {
message: 'Hello from ParentComponent!',
};
render() {
return (
<MyContext.Provider value={this.state.message}>
<ChildComponent />
</MyContext.Provider>
);
}
}
// 底层组件消费数据
const ChildComponent = () => {
return (
<MyContext.Consumer>
{value => <p>{value}</p>}
</MyContext.Consumer>
);
}
// 渲染
ReactDOM.render(<ParentComponent />, document.getElementById('root'));
在此例中,ParentComponent提供了一个message数据,并通过MyContext.Provider传递给ChildComponent,ChildComponent通过MyContext.Consumer来消费并显示message的值。
在函数式组件中可以使用useContext来实现,具体可以看看这篇react中的context实现深层数据传递
3. react中如何加载异步组件
在 React 中加载异步组件通常使用 React.lazy() 方法。React.lazy() 接收一个函数,这个函数需要动态 import 这个组件,返回一个 Promise,React 将在组件加载完成后渲染它。
举例说明:
javascript
import React, { Suspense } from 'react';
const AsyncComponent = React.lazy(() => import('./AsyncComponent'));
function App() {
return (
<div>
<h1>异步组件加载示例</h1>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</div>
);
}
export default App;
在上面的示例中,我们创建了一个 App 组件,通过 React.lazy() 方法加载了一个异步组件 AsyncComponent。在 Suspense 组件中,我们可以设置一个 fallback 属性,在组件加载完成前会显示该属性的内容。
需要注意的是,React.lazy() 目前只支持默认导出的组件,所以被异步加载的组件需要使用默认导出。
4. shouldComponentUpdate有什么用
shouldComponentUpdate是React组件中一个重要的生命周期函数,用于控制组件的更新频率。当组件的props或state发生变化时,React会调用shouldComponentUpdate方法来判断是否需要重新渲染组件。如果shouldComponentUpdate返回false,React则会阻止组件的更新,从而提高性能。
举个例子,假设有一个列表组件List,当用户点击某个按钮时,会向列表中添加一个新的项。但是,如果列表中已经包含相同的项,则不需要更新列表。在这种情况下,我们可以通过shouldComponentUpdate来判断新的项是否已经存在于列表中,如果存在则返回false,否则返回true。
js
class List extends React.Component {
shouldComponentUpdate(nextProps) {
// 检查新的项是否已经存在于列表中
if (this.props.items.includes(nextProps.newItem)) {
return false;
}
return true;
}
render() {
return (
<ul>
{this.props.items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
}
}
在上面的例子中,当shouldComponentUpdate返回false时,新的项不会被添加到列表中,从而避免不必要的组件更新。这样可以减少组件的渲染次数,提高性能。
5. state中值的不可变性
在React中,保持原有state值的不可变性是为了确保组件的可预测性和性能。如果直接修改state中的值,可能会导致不可预测的行为,因为React可能无法正确地检测到state的变化,进而无法正确地重新渲染组件。
通过确保state的不可变性,可以减少出现bug的可能性,因为我们可以清晰地追踪state的变化。此外,React使用了一个称为"浅比较"的机制来检测state的变化,如果直接修改state中的值,可能会导致React无法正确地检测到state的变化,导致组件不会重新渲染,从而影响性能。
因此,在使用setState更新state时,应该始终创建一个新的对象或数组,而不是直接修改原始state,以确保state的不可变性和组件的可靠性。
举例来说,假设有一个组件的state中有一个数组:
js
this.state = {
numbers: [1, 2, 3, 4, 5]
}
如果我们想将数组中第一个元素改为6,正确的做法是先创建一个新的数组,然后再更新state:
js
const newNumbers = [...this.state.numbers];
newNumbers[0] = 6;
this.setState({numbers: newNumbers});
如果直接修改state中的数组,例如:
js
this.state.numbers[0] = 6;
this.setState({numbers: this.state.numbers});
这种方式会直接修改原有的state中的数组,可能导致React无法正确检测到state的变化,从而导致界面没有得到正确的更新。
这是因为React在进行state更新时,会进行引用比较来确定是否需要重新渲染组件。如果直接修改state中的数组而没有保持其不可变性,即没有创建一个新的数组来替换原有的数组,React可能会认为引用未发生改变,从而不会触发重新渲染。这样就会导致界面没有得到正确的更新,因为实际上state中的数据已经发生了变化,但React并没有正确检测到这一点。因此,在React中,强调保持原有值的不可变性是为了确保React能够正确地检测到state的变化并触发组件的重新渲染。
因此我们在操作数组时,要使用返回新数组的方法
常用的JS数组方法中,会返回新数组的方法有:
- map()
- filter()
- concat()
- slice()
- flat()
- reduce()
会修改原数组的方法有:
- pop()
- push()
- shift()
- unshift()
- splice()
- reverse()
- sort()
6. HOC和Render Props代码复用和逻辑分离
HOC(Higher Order Component)和Render Props都是React中常用的模式,用于组件之间的代码复用和逻辑分离。
HOC是一个函数,接受一个组件作为参数并返回一个新的增强组件。通过HOC可以将通用的逻辑封装到一个函数中,然后在多个地方重复使用。例如,下面是一个HOC,用于给组件添加loading状态:
js
const withLoading = (WrappedComponent) => {
return class WithLoading extends React.Component {
state = {
loading: true
}
componentDidMount() {
setTimeout(() => {
this.setState({ loading: false });
}, 2000);
}
render() {
return this.state.loading ? <div>Loading...</div> : <WrappedComponent {...this.props} />;
}
}
}
Render Props则是通过组件的props将函数传递给子组件,子组件可以调用该函数来获取值或者逻辑。通过Render Props可以更加灵活地共享代码逻辑。下面是一个Render Props的示例,用于共享鼠标坐标:
js
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({ x: event.clientX, y: event.clientY });
}
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.children(this.state)}
</div>
);
}
}
const App = () => (
<MouseTracker>
{({ x, y }) => (
<div>
<h1>Mouse coordinates:</h1>
<p>X: {x}, Y: {y}</p>
</div>
)}
</MouseTracker>
);
在上面的例子中,MouseTracker组件通过props.children将函数传递给子组件,子组件可以通过调用该函数获取到鼠标坐标的值。
7. redux
Redux是一个用于JavaScript应用程序的开源状态管理库,它被设计为一个可预测状态容器,使得应用的状态管理更加可控并且易于调试。
Redux的主要用途是管理应用程序的状态,例如用户的登录状态、页面的加载状态以及组件之间的通信等。通过Redux,我们可以将应用程序的状态统一管理在一个容器中,通过定义action、reducer和store来管理数据的流动,从而确保状态的一致性和可控性。
举例说明,我们可以创建一个简单的计数器应用程序来演示Redux的用法:
首先,我们定义一个action,例如增加计数器的动作:
javascript
const increment = () => {
return {
type: 'INCREMENT'
};
};
然后,我们定义一个reducer来处理action,并更新应用程序的状态:
javascript
const counterReducer = (state = 0, action) => {
switch(action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
};
接着,我们创建一个Redux store,并将reducer注册到store中:
javascript
import { createStore } from 'redux';
const store = createStore(counterReducer);
最后,在应用程序中,我们可以通过dispatch一个action来更新应用的状态,并通过getState方法获取最新的状态值:
javascript
store.dispatch(increment());
console.log(store.getState()); // 输出1
通过以上示例,我们可以看到Redux通过action和reducer来管理应用程序的状态,使得状态的变化变得可预测和容易管理。
Action Reducer State View
8.React-Redux
React-Redux 是一个用于 React 应用程序的 JavaScript 库,用于管理应用程序状态并帮助在组件之间传递数据。React-Redux 绑定了 Redux 的状态管理功能与 React 组件,使得在 React 应用程序中使用 Redux 变得更加简单和高效。
使用 React-Redux 有以下几个主要的优势:
- 提供了一个统一的状态管理中心,使得应用程序的状态更加集中化和易于维护。
- 可以避免将状态传递多个组件层级,通过 React-Redux 提供的 connect 函数,可以直接从状态管理中心获取数据。
- 帮助优化组件的性能,可以避免不必要的渲染和数据更新。
使用 React-Redux 的基本步骤如下:
- 安装 React-Redux:在项目中安装 react-redux 包,可以使用 npm 或者 yarn 进行安装。
- 创建 Redux store:在项目中创建 Redux store,并定义相关的 reducer、action 和 middleware。
- 使用 Provider:在根组件中使用 Provider 组件,并将创建的 Redux store 作为 props 传递给 Provider。
- 使用 connect 函数:在需要访问 Redux store 数据的组件中,使用 connect 函数来连接组件和 Redux store,将状态作为 props 传递给组件。
举例说明:
js
// 定义 reducer
const counterReducer = (state = 0, action) => {
switch(action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
// 创建 Redux store
const store = createStore(counterReducer);
// 创建 React 组件
const Counter = ({ count, increment, decrement }) => (
<div>
<h1>{count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
// 连接组件和 Redux store
const mapStateToProps = state => ({
count: state
});
const mapDispatchToProps = dispatch => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
});
const ConnectedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);
// 在根组件中使用 Provider
const App = () => (
<Provider store={store}>
<ConnectedCounter />
</Provider>
);
在以上的示例中,我们创建了一个简单的计数器应用程序,使用了 React-Redux 进行状态管理。通过 connect 函数连接了 Counter 组件和 Redux store,并使用 Provider 在根组件中传递了 Redux store。当点击 Increment 和 Decrement 按钮时,会分别触发对应的 action,从而更新组件中的状态。
9. react-reducx异步action
在React-Redux中使用异步action通常需要借助中间件,最常用的中间件是redux-thunk。redux-thunk允许action创建函数返回一个函数而不是一个action对象,这样就可以在返回的函数中进行异步操作。
下面是一个使用redux-thunk的异步action的示例:
javascript
// actions.js
const fetchPostsRequest = () => {
return {
type: 'FETCH_POSTS_REQUEST'
}
}
const fetchPostsSuccess = (posts) => {
return {
type: 'FETCH_POSTS_SUCCESS',
payload: posts
}
}
const fetchPostsError = (error) => {
return {
type: 'FETCH_POSTS_ERROR',
payload: error
}
}
export const fetchPosts = () => {
return (dispatch) => {
dispatch(fetchPostsRequest());
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => {
dispatch(fetchPostsSuccess(data));
})
.catch(error => {
dispatch(fetchPostsError(error));
});
}
}
// reducer.js
const initialState = {
posts: [],
loading: false,
error: null
}
const postsReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_POSTS_REQUEST':
return {
...state,
loading: true
}
case 'FETCH_POSTS_SUCCESS':
return {
...state,
loading: false,
posts: action.payload,
error: null
}
case 'FETCH_POSTS_ERROR':
return {
...state,
loading: false,
error: action.payload
}
default:
return state
}
}
export default postsReducer;
在上面的示例中,fetchPosts函数是一个异步action创建函数,它返回一个函数,这个函数内部执行异步操作,获取数据后分发相应的成功或失败action。在reducer中根据不同的action类型更新state。
需要注意的是,在创建store时要应用redux-thunk中间件,示例代码如下:
javascript
// store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
这样就可以在React组件中dispatch这个fetchPosts函数来获取数据,并在数据获取成功或失败后更新UI。