Redux与React集成及服务器端渲染全解析
1. Redux与React集成:绑定动作到组件事件处理程序
在将Redux与React集成时,为了让应用对用户操作做出响应,需要使用
mapDispatchToProps
函数。该函数会接收一个
dispatch
参数,此参数为存储的
dispatch
方法,并将其注入到组件中。即便不提供
mapDispatchToProps
函数,容器的
props
中也会自动注入
dispatch
方法,但使用
mapDispatchToProps
能将特定于组件的动作逻辑与组件本身分离,便于测试。
mapDispatchToProps
函数会被
react-redux
调用,其返回的对象会合并到组件的
props
中。借助这个函数可以设置动作创建器,并使其在组件中可用。同时,可以利用Redux的
bindActionCreators
辅助工具,它能将值为动作创建器的对象转换为具有相同键的对象,不同之处在于每个动作创建器都被包裹在
dispatch
调用中,可直接调用。
以下是一个使用
mapDispatchToProps
的示例代码:
import { createError } from '../actions/error';
import { createNewPost, getPostsForPage } from '../actions/posts';
import { showComments } from '../actions/comments';
import Ad from '../components/ad/Ad';
import CreatePost from '../components/post/Create';
import Post from '../components/post/Post';
import Welcome from '../components/welcome/Welcome';
export class Home extends Component {
componentDidMount() {
this.props.actions.getPostsForPage();
}
componentDidCatch(err, info) {
this.props.actions.createError(err, info);
}
render() {
return (
<div className="home">
<Welcome />
<div>
<CreatePost onSubmit={this.props.actions.createNewPost} />
{this.props.posts && (
<div className="posts">
{this.props.posts.map(post => (
<Post
key={post.id}
post={post}
openCommentsDrawer={this.props.actions.showComments}
/>
))}
</div>
)}
<button className="block"
onClick={this.props.actions.getNextPageOfPosts}>
Load more posts
</button>
</div>
<div>
<Ad url="https://ifelse.io/book" imageUrl="/static/assets/ads/ria.png" />
<Ad url="https://ifelse.io/book" imageUrl="/static/assets/ads/orly.jpg" />
</div>
</div>
);
}
}
export const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators(
{
createNewPost,
getPostsForPage,
showComments,
createError,
getNextPageOfPosts: getPostsForPage.bind(this, 'next')
},
dispatch
)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
已连接到Redux的组件还有以下这些:
| 组件名称 | 文件路径 |
| ---- | ---- |
| App | src/app.js |
| Comments | src/components/comment/Comments.js |
| Error | src/components/error/Error.js |
| Navigation | src/components/nav/navbar.js |
| PostActionSection | src/components/post/PostActionSection.js |
| Posts | src/components/post/Posts.js |
| Login | src/pages/login.js |
| SinglePost | src/pages/post.js |
2. 更新测试
将
Home
组件转换为使用Redux后,之前编写的测试会失效,需要进行更新。以下是更新后的测试文件示例:
jest.mock('mapbox');
import React from 'react';
import renderer from 'react-test-renderer';
import { Provider } from 'react-redux';
import { Home, mapStateToProps, mapDispatchToProps } from '../../src/pages/home';
import configureStore from '../../src/store/configureStore';
import initialState from '../../src/constants/initialState';
const now = new Date().getTime();
describe('Single post page', () => {
const state = Object.assign({}, initialState, {
posts: {
2: { content: 'stuff', likes: [], date: now },
1: { content: 'stuff', likes: [], date: now }
},
postIds: [1, 2]
});
const store = configureStore(state);
test('mapStateToProps', () => {
expect(mapStateToProps(state)).toEqual({
posts: [
{ content: 'stuff', likes: [], date: now },
{ content: 'stuff', likes: [], date: now }
]
});
});
test('mapDispatchToProps', () => {
const dispatchStub = jest.fn();
const mappedDispatch = mapDispatchToProps(dispatchStub);
expect(mappedDispatch.actions.createNewPost).toBeDefined();
expect(mappedDispatch.actions.getPostsForPage).toBeDefined();
expect(mappedDispatch.actions.showComments).toBeDefined();
expect(mappedDispatch.actions.createError).toBeDefined();
expect(mappedDispatch.actions.getNextPageOfPosts).toBeDefined();
});
test('should render posts', function() {
const props = {
posts: [
{ id: 1, content: 'stuff', likes: [], date: now },
{ id: 2, content: 'stuff', likes: [], date: now }
],
actions: {
getPostsForPage: jest.fn(),
createNewPost: jest.fn(),
createError: jest.fn(),
showComments: jest.fn()
}
};
const component = renderer.create(
<Provider store={store}>
<Home {...props} />
</Provider>
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
3. 服务器端渲染概述
React支持通过React DOM的服务器API进行服务器端渲染(SSR)。SSR通常是指生成静态HTML标记,可通过HTTP或其他协议发送到浏览器,这是在服务器环境下的“渲染”操作。在某些情况下,将SSR集成到应用中是有用的,而在其他情况下则可能不必要。
在过去,许多应用仅使用服务器渲染视图,服务器会创建包含用户相关或其他数据的HTML字符串,并通过HTTP发送到浏览器。起初,服务器端脚本比较原始,手动拼接HTML字符串,这不仅耗时,而且难以更改。随着时间的推移,出现了许多框架和语言,帮助开发者构建主要在服务器上渲染的用户界面。
以下是服务器端渲染的简单流程:
graph LR
A[浏览器请求] --> B[服务器数据查找/计算]
B --> C[最终格式化、模板处理]
C --> D[服务器响应:动态生成的HTML]
D --> E[浏览器]
4. 深入了解服务器端渲染
在实现SSR之前,先了解非React上下文中的SSR方面知识是有帮助的。以使用ERB(嵌入式Ruby)的SSR为例,ERB是Ruby编程语言的一个特性,可用于创建HTML模板。许多Ruby on Rails应用会使用ERB模板生成视图,框架会读取开发者创建的
.erb
模板文件,并使用服务器或其他地方的数据填充它们,最终将填充好数据的文本发送到用户浏览器。
以下是一个简单的ERB文件示例:
<h1>Listing Books</h1>
<table>
<tr>
<th>Title</th>
<th>Summary</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @books.each do |book| %>
<tr>
<td><%= book.title %></td>
<td><%= book.content %></td>
<td><%= link_to "Show", book %></td>
<td><%= link_to "Edit", edit_book_path(book) %></td>
<td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td>
</tr>
<% end %>
</table>
<br>
<%= link_to "New book", new_book_path %>
服务器处理完这样的模板后,会向浏览器发送文本响应。以下是一个HTTP(版本1/1.1)响应的文本表示示例:
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.51.0
> Accept: */*
< HTTP/1.1 200 OK
< Cache-Control: max-age=604800
< Content-Type: text/html
< Date: Mon, 01 May 2017 16:34:13 GMT
< Etag: "359670651+gzip+ident"
< Expires: Mon, 08 May 2017 16:34:13 GMT
综上所述,通过将Redux与React集成,能够更好地管理应用状态,而服务器端渲染则为应用带来了更好的性能和SEO等优势。在实际开发中,可以根据具体需求选择合适的技术和方法。
5. 服务器端渲染的优势与应用场景分析
服务器端渲染具有多方面的优势,以下为详细分析:
-
性能优化
:SSR可以在服务器端生成完整的HTML页面,浏览器接收到后可以直接渲染,减少了客户端的加载时间,尤其对于性能较差的设备和网络环境,能显著提升用户体验。
-
SEO友好
:搜索引擎爬虫可以直接抓取服务器端生成的HTML内容,有利于提高网站在搜索引擎中的排名。
-
首屏加载速度快
:用户在请求页面时,能更快地看到完整的页面内容,而不是等待客户端JavaScript加载和执行后再渲染。
然而,服务器端渲染并非适用于所有场景,以下是一些适用和不适用的场景分析:
| 适用场景 | 不适用场景 |
| ---- | ---- |
| 对SEO要求较高的网站,如新闻网站、电商网站等 | 对实时性要求极高的应用,如在线游戏、实时聊天等 |
| 内容更新不频繁但需要快速加载的页面 | 简单的静态页面,使用SSR会增加服务器负担 |
| 客户端性能有限的设备访问的网站 | 开发资源有限,无法承担SSR开发和维护成本的项目 |
6. 过渡到React Router
为了更好地支持服务器端渲染和未来的改进,可以将现有的路由设置过渡到React Router。React Router是一个用于React应用的强大路由库,提供了丰富的功能和灵活的配置选项。
以下是过渡到React Router的基本步骤:
1.
安装React Router
:使用npm或yarn安装React Router库。
npm install react-router-dom
- 创建路由配置 :在应用中创建路由配置文件,定义不同路径对应的组件。
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
const AppRouter = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
};
export default AppRouter;
- 在应用中使用路由 :在根组件中引入路由配置。
import React from 'react';
import ReactDOM from 'react-dom/client';
import AppRouter from './AppRouter';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<AppRouter />);
7. 使用React Router处理认证路由
在实际应用中,有些页面需要用户登录后才能访问,这就需要处理认证路由。以下是使用React Router处理认证路由的示例:
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import Login from './pages/Login';
// 模拟用户认证状态
const isAuthenticated = false;
const PrivateRoute = ({ children }) => {
return isAuthenticated ? children : <Navigate to="/login" />;
};
const AppRouter = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={
<PrivateRoute>
<Dashboard />
</PrivateRoute>
} />
</Routes>
</Router>
);
};
export default AppRouter;
在上述代码中,
PrivateRoute
组件用于检查用户是否已认证,如果未认证则重定向到登录页面。
8. 服务器端渲染过程中获取数据
在服务器端渲染过程中,可能需要获取一些数据来填充页面。可以使用异步操作来获取数据,并在服务器端等待数据加载完成后再生成HTML页面。
以下是一个简单的示例,展示如何在服务器端渲染过程中获取数据:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import axios from 'axios';
import Home from './pages/Home';
const serverRender = async () => {
try {
// 模拟异步数据获取
const response = await axios.get('https://api.example.com/data');
const data = response.data;
// 将数据传递给组件
const html = ReactDOMServer.renderToString(<Home data={data} />);
return html;
} catch (error) {
console.error('Error fetching data:', error);
return '<div>Error fetching data</div>';
}
};
export default serverRender;
在上述代码中,使用
axios
库发送异步请求获取数据,然后将数据传递给
Home
组件,并使用
ReactDOMServer.renderToString
方法将组件渲染为HTML字符串。
9. 在服务器端渲染过程中使用Redux
在服务器端渲染过程中使用Redux可以更好地管理应用状态。以下是在服务器端渲染过程中使用Redux的基本步骤:
1.
创建Redux store
:在服务器端创建Redux store,并初始化状态。
import { createStore } from 'redux';
import rootReducer from './reducers';
const initialState = {};
const store = createStore(rootReducer, initialState);
export default store;
- 在服务器端获取数据并更新store :在服务器端获取数据后,使用action creators更新store的状态。
import axios from 'axios';
import { fetchDataSuccess } from './actions';
import store from './store';
const serverRender = async () => {
try {
const response = await axios.get('https://api.example.com/data');
const data = response.data;
// 更新store的状态
store.dispatch(fetchDataSuccess(data));
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<Home />
</Provider>
);
return html;
} catch (error) {
console.error('Error fetching data:', error);
return '<div>Error fetching data</div>';
}
};
export default serverRender;
- 将store状态传递给客户端 :在服务器端将store的状态传递给客户端,以便客户端可以继续使用相同的状态。
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<Home />
</Provider>
);
const preloadedState = store.getState();
const responseHtml = `
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root">${html}</div>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')}
</script>
<script src="client.js"></script>
</body>
</html>
`;
res.send(responseHtml);
通过以上步骤,可以在服务器端渲染过程中有效地使用Redux管理应用状态。
综上所述,通过将Redux与React集成、使用服务器端渲染以及过渡到React Router等技术,可以构建出性能更优、功能更强大的Web应用。在实际开发中,需要根据具体需求和场景选择合适的技术和方法,并不断优化和改进应用。
超级会员免费看
616

被折叠的 条评论
为什么被折叠?



