文章目录
- 1. Portals将子组件渲染到父组件以外的DOM节点上
- 2. Context组件树中传递数据
- 3. react中如何加载异步组件
- 4. shouldComponentUpdate有什么用
- 5. state中值的不可变性
- 6. HOC和Render Props代码复用和逻辑分离
- 7. redux
- 8.React-Redux
- 9. react-reducx异步action
1. Portals将子组件渲染到父组件以外的DOM节点上
在React中,Portals是一种机制,可以将子组件渲染到父组件以外的DOM节点上。通常情况下,React组件会被渲染到它们的父组件的DOM结构中,但有时我们希望将某个组件渲染到DOM结构的其他位置或根节点上,这时就可以使用Portals。
Portals的主要用途包括但不限于以下几点:
- 在应用中的任意位置渲染组件,无需将所有组件都嵌套在同一个层级结构中。
- 在模态框或弹出窗口等场景下,将组件渲染到最顶层的DOM节点上,确保它们处于最前面且不受其他布局影响。
- 在复杂布局下,将组件渲染到特定的DOM节点上,实现更灵活的布局控制。
举个例子,假设我们有一个模态框组件Modal,并希望将其渲染到页面的最顶层节点上,可以使用Portals实现:
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就会很方便。
// 创建一个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 将在组件加载完成后渲染它。
举例说明:
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。
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中有一个数组:
this.state = {numbers: [1, 2, 3, 4, 5]
}
如果我们想将数组中第一个元素改为6,正确的做法是先创建一个新的数组,然后再更新state:
const newNumbers = [...this.state.numbers];
newNumbers[0] = 6;this.setState({numbers: newNumbers});
如果直接修改state中的数组,例如:
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状态:
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的示例,用于共享鼠标坐标:
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,例如增加计数器的动作:
const increment = () => {return {type: 'INCREMENT'};
};
然后,我们定义一个reducer来处理action,并更新应用程序的状态:
const counterReducer = (state = 0, action) => {switch(action.type) {case 'INCREMENT':return state + 1;default:return state;}
};
接着,我们创建一个Redux store,并将reducer注册到store中:
import { createStore } from 'redux';const store = createStore(counterReducer);
最后,在应用程序中,我们可以通过dispatch一个action来更新应用的状态,并通过getState方法获取最新的状态值:
store.dispatch(increment());
console.log(store.getState()); // 输出1
通过以上示例,我们可以看到Redux通过action和reducer来管理应用程序的状态,使得状态的变化变得可预测和容易管理。
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 传递给组件。
举例说明:
// 定义 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的示例:
// 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中间件,示例代码如下:
// 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。