周常3 算法题5道、react ssr 补充
周常
算法题 java 实现
1.调整数组顺序使奇数位于偶数前面
2.链表中倒数第k个结点
3.翻转链表
4.合并两个排序的链表
5.树的子结构react ssr 补充
算法题
调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变
解题思路
1.前后两个指针从头尾出发
2.判断两个指针序号的奇偶数
3.左右指针不接触时进行运算
4.左指针序号自增直到找到偶数,右指针序号自减直到找到奇数。
5.左指针序号小于右指针序号则两者未接触,交换位置。
代码实现
1 | public class ReorderArray { |
链表中倒数第k个结点
链表中倒数第k个节点
解题思路
用两个链表使用双指针解法
1.第一个指针先走 k 步
2.第二个指针和第一个指针同时走,当第一个指针走到最后一位时一起停止
3.这时候第二个指针还有 k 步没走,第二个指针当前的位置就是倒数的 k 的位置
代码实现
1 | public class FindKthToTail { |
翻转链表
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解题思路
1.创建一个 null 链表 pre
2.每次把原链表 cur 的第一位放在pre 的第一位
3.直到原链表 cur 为 null 时
1 | // 第一次 |
代码实现
1 | public class ReverseList { |
合并两个排序的链表
解题思路
- 解法1 使用循环
1.创建虚拟头 dummy
2.dummy 引用赋值给 cur
3.当 l1 l2 都不为 null 时循环
4.比较 l1.val 和 l2.val 谁小谁接入到 cur.next 上,ln = ln.next 继续比较下一个
- cur = cur.next 同时向下准备介接入
- 当l1 或 l2 其中一个为空时 cur.next 为剩下不为空的链表
7.最后返回 dummy 引用的 dummy.next
- 解法2 使用递归
1.基础问题:当其中一个链表为空时,剩下的节点肯定来自另一个链表
2.基本问题:比较 l1.val 和 l2.val,谁小谁的 next 就递归比较其 next 节点与另一个链表的合并.
代码实现
1 | public class MergeTwoSortedLists { |
树的子结构
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
解题思路
1.创建 equals 函数,比较两个 TreeNode, 比较两个 TreeNode 的 val,如果相等递归比较 left 和 right 节点
2.创建 traverse 函数,这个函数用来在 s 的每个节点中比较 t ,同时进行比较当前节点, 递归的在 s 的 left 和 right 里比较 t
代码实现
1 | public class SubtreeOfAnotherTree { |
react ssr 补充
node server 中间层
之前的代码在渲染时 node server 请求了一次 api 返回了数据插入到页面上,client 的代码在 componentDidMount 里也是请求了 api ,这次改造成 client 请求 node server,node server 再去请求 api,让 node server 只做代理而不是客户端还去请求外部服务。
使用 express-http-proxy
设置代理 client 请求本地服务 /api/news.json?secret=abcd 时代理到 http://47.95.113.63/ssr/api/news.json?secret=abcd 下
1 | import proxy from 'express-http-proxy'; |
区分 server client axios 请求的 baseURL
server client axios 配置
1
2
3
4
5
6
7
8// client
import axios from 'axios';
const instance = axios.create({
baseURL: '/'
});
export default instance;1
2
3
4
5
6
7
8
9
10
11// server
import axios from 'axios';
const createInstance = (req) => axios.create({
baseURL: 'http://47.95.113.63/ssr',
headers: {
cookie: req.get('cookie') || ''
}
});
export default createInstance;使用 thunk.withExtraArgument
1
2
3
4
5
6
7
8
9
10export const getStore = (req) => {
// 改变服务器端store的内容,那么就一定要使用serverAxios
return createStore(reducer, applyMiddleware(thunk.withExtraArgument(serverAxios(req))));
}
export const getClientStore = () => {
const defaultState = window.context.state;
// 改变客户端store的内容,一定要使用clientAxios
return createStore(reducer, defaultState, applyMiddleware(thunk.withExtraArgument(clientAxios)));
}action 上就会增加额外的参数
1
2
3
4
5
6
7
8
9
10// action
export const getHomeList = () => {
return (dispatch, getState, axiosInstance) => {
return axiosInstance.get('/api/news.json?secret=abcd')
.then((res) => {
const list = res.data.data;
dispatch(changeList(list))
});
}
}或者使用 webpack.DefinePlugin 插件,给server 端代码增加环境变量
1
2// 通过插件传递的环境变量只在 server 打包的代码里使用
const baseUrl = process.env.API_BASE || ''1
2
3
4
5
6// webpack.server.js
plugins: [
new webpack.DefinePlugin({
'process.env.API_BASE': '"http://127.0.0.1:3333"'
})
]
多层路由展示
1 | // Routes.js |
1 | import { renderRoutes } from 'react-router-config'; |
一级路由组件要渲染二级路由 routes 子路由数组
1 | routes: [ |
一级路由组件渲染后 route 属性可以在 props 里获取到,在一级路由组件里使用 renderRoutes 渲染一级路由的 props.route.routes
1 | // 一级路由的组件 |
解决 cookie 携带问题
当浏览器请求本地 node 服务时(携带 cookie)
node server 进行服务端渲染转发浏览器的请求
node server 请求 api server 获取数据(cookie 需要手动携带)
express-http-proxy 中间件 只转发了请求路径,请求内容最终还是 axios 请求的。
1 | app.use('/api', proxy('http://47.95.113.63', { |
通过把 express req 对象传递给 getStore 最后改造 createInstance 为高阶函数,这样cookie 就可以给 axios 获取cookie了
1 | app.get('*', function (req, res) { |
生成404页面
路由配置
1 | export default [{ |
解决请求返回的页面 status 404 返回
在 server 使用 StaticRouter 传递 context, context 可以从 props.staticContext 中获取
修改 server ssr 路由配置,给 render 函数传递 context
1 | app.get('*', function (req, res) { |
给 render 函数增加 context 参数传递到 StaticRouter 组件中,等到 props.staticContext 使用
1 |
|
修改 NotFound 页面, 当没有匹配路由访问到 NotFound 页面时,staticContext 增加 NOT_FOUND
因为 server client 都会执行,client 不存在 staticContext 记得判断一下防止报错
1 | import React, { Component } from 'react'; |
这样我们可以通过判断 context.NOT_FOUND 来判断设置 status 404再返回页面
1 | app.get('*', function (req, res) { |
实现 301 重定向
没登录的情况下访问需要授权的页面 虽然 client 的逻辑重定向了,但是server 端代码没有重定向。
1 | // client 页面执行的 Redirect |
不过 renderRoutes 方法在发现 Redirect 组件执行时,就是在 server 上发现时会帮我们的 context 上塞一段数据用来判断
1 | // context: { url: '', action: '', location: { pathname: '', search: '', hash: '', state: undefined } } |
这样就可以实现301了
错误获取
当 node server 代理的请求报错时页面就崩溃了,我们喜欢 node server 代理的请求有错时还是能返回没报错的数据同时把页面渲染出来
1 | app.get('*', function (req, res) { |
处理 CSS
server 打包 处理 css
使用isomorphic-style-loader, 处理样式的 loader 需要 window 对象,server 端是没有 window 的,使用同构样式的 loader, 这里使用 modules 模式需要执行 JS 才有样式,会有白屏问题
1 | rules: [{ |
实现 ssr 样式
- 创建传递 styles.getCss() 对象到 staticContext.css 里的高阶组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import React, { Component } from 'react';
export default (DecoratedComponent, styles) => {
return class NewComponent extends Component {
componentWillMount() {
if (this.props.staticContext) {
this.props.staticContext.css.push(styles._getCss());
}
}
render() {
return <DecoratedComponent {...this.props} />
}
}
} - ssr 样式的页面使用高阶组件
这里同时把 loadData 挂载到到处的页面对象上。1
2
3
4
5
6
7const ExportHome = connect(mapStateToProps, mapDispatchToProps)(withStyle(Home, styles)); // 使用 withStyle
ExportHome.loadData = (store) => {
return store.dispatch(getHomeList())
}
export default ExportHome; - 最后在 server 里修改返回的模板
通过传入的 staticContext 对象里找到 css 对象数组,转换成字符串后,插入到 head 里1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33export const render = (store, routes, req, context) => {
const content = renderToString((
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>
{renderRoutes(routes)}
</div>
</StaticRouter>
</Provider>
));
const cssStr = context.css.length ? context.css.join('\n') : '';
return `
<html>
<head>
<title>ssr</title>
<style>${cssStr}</style>
</head>
<body>
<div id="root">${content}</div>
<script>
window.context = {
state: ${JSON.stringify(store.getState())}
}
</script>
<script src='/index.js'></script>
</body>
</html>
`;
}
SEO
Title 和 Description
1 | <title> 标题 |
React-Helment
- page 上使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// pages/home.js
import { Helmet } from "react-helmet";
class Home extends Component {
render() {
return (
<Fragment>
<Helmet>
<title>新闻页面 - 丰富多彩的资讯</title>
<meta name="description" content="新闻页面 - 丰富多彩的资讯" />
</Helmet>
<div className={styles.container}>
{list}
</div>
</Fragment>
)
}
} - node server 上使用
注意要在 react.renderToString() 后使用 const helmet = Helmet.renderStatic(); 来获取每个 page 中 react 组件设置的 Helmet 对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import { Helmet } from "react-helmet";
export const render = (store, routes, req, context) => {
const content = renderToString((
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<div>
{renderRoutes(routes)}
</div>
</StaticRouter>
</Provider>
));
const helmet = Helmet.renderStatic();
const cssStr = context.css.length ? context.css.join('\n') : '';
return `
<html>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
<style>${cssStr}</style>
</head>
<body>
<div id="root">${content}</div>
<script>
window.context = {
state: ${JSON.stringify(store.getState())}
}
</script>
<script src='/index.js'></script>
</body>
</html>
`;
}
预渲染解决 SEO
访问一个普通 react 项目链接,爬虫蜘蛛也访问项目链接
可以通过识别爬虫蜘蛛访问的时候预渲染 dom 返回给爬虫。
prerender 模块
创建 prerender 服务器
访问时加上查询参数 ?url=${url} 预渲染服务就会先去访问 url 再返回给爬虫1
2
3
4
5const prerender = require('prerender');
const server = prerender({
port: 8000
});
server.start();使用 nginx 识别用户和爬虫
nginx 识别爬虫就访问 prerender,识别是人就访问 react 项目prerender 官网参考
引用