commit
8a7623d281
|
@ -66,7 +66,9 @@ export function getBaseHistory<S>(
|
||||||
Object.assign(historyProps, nextState);
|
Object.assign(historyProps, nextState);
|
||||||
}
|
}
|
||||||
historyProps.length = browserHistory.length;
|
historyProps.length = browserHistory.length;
|
||||||
const args = { location: historyProps.location, action: historyProps.action };
|
// 避免location饮用相同时setState不触发
|
||||||
|
const location = Object.assign({}, historyProps.location);
|
||||||
|
const args = { location: location, action: historyProps.action };
|
||||||
transitionManager.notifyListeners(args);
|
transitionManager.notifyListeners(args);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,13 @@ export function createHashHistory<S = DefaultStateType>(option: HashHistoryOptio
|
||||||
const pathDecoder = addHeadSlash;
|
const pathDecoder = addHeadSlash;
|
||||||
const pathEncoder = hashType === 'slash' ? addHeadSlash : stripHeadSlash;
|
const pathEncoder = hashType === 'slash' ? addHeadSlash : stripHeadSlash;
|
||||||
|
|
||||||
|
const startLocation = getHashContent(window.location.href);
|
||||||
|
const encodeLocation = pathEncoder(startLocation);
|
||||||
|
// 初始化hash格式不合法时会重定向
|
||||||
|
if (startLocation !== encodeLocation) {
|
||||||
|
window.location.replace(stripHash(window.location.href) + '#' + encodeLocation);
|
||||||
|
}
|
||||||
|
|
||||||
function getLocation() {
|
function getLocation() {
|
||||||
let hashPath = pathDecoder(getHashContent(window.location.hash));
|
let hashPath = pathDecoder(getHashContent(window.location.hash));
|
||||||
if (basename) {
|
if (basename) {
|
||||||
|
|
|
@ -121,7 +121,13 @@ describe('parser test', () => {
|
||||||
it('url without end slash match wildcard', function () {
|
it('url without end slash match wildcard', function () {
|
||||||
const parser = createPathParser('/about/', { strictMode: false });
|
const parser = createPathParser('/about/', { strictMode: false });
|
||||||
const matched = parser.parse('/about');
|
const matched = parser.parse('/about');
|
||||||
expect(matched).toBeNull();
|
expect(matched).toStrictEqual({
|
||||||
|
path: '/about/',
|
||||||
|
url: '/about',
|
||||||
|
score: [10],
|
||||||
|
isExact: true,
|
||||||
|
param: {},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('url without end slash match wildcard (strictMode)', function () {
|
it('url without end slash match wildcard (strictMode)', function () {
|
||||||
|
@ -259,7 +265,7 @@ describe('parser test', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('dynamic param with complex regexp pattern', () => {
|
it('dynamic param with complex regexp pattern', () => {
|
||||||
const parser = createPathParser('/detail/:action([\\da-z]+)', { exact: true });
|
const parser = createPathParser('/detail/:action([\\da-z]+)', { exact: true, caseSensitive: true });
|
||||||
const res = parser.parse('/detail/a123');
|
const res = parser.parse('/detail/a123');
|
||||||
expect(res).toEqual({
|
expect(res).toEqual({
|
||||||
isExact: true,
|
isExact: true,
|
||||||
|
|
|
@ -97,12 +97,14 @@ export function createPathParser<P = unknown>(pathname: string, option: ParserOp
|
||||||
const token = tokens[tokenIdx];
|
const token = tokens[tokenIdx];
|
||||||
const nextToken = tokens[tokenIdx + 1];
|
const nextToken = tokens[tokenIdx + 1];
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case TokenType.Delimiter:
|
case TokenType.Delimiter: {
|
||||||
{
|
// 该分隔符后有可选参数则该分割符在匹配时是可选的
|
||||||
const hasOptional = lookToNextDelimiter(tokenIdx + 1);
|
const hasOptional = lookToNextDelimiter(tokenIdx + 1);
|
||||||
pattern += `/${hasOptional ? '?' : ''}`;
|
// 该分割符为最后一个且strictMode===false时,该分隔符在匹配时是可选的
|
||||||
}
|
const isSlashOptional = nextToken === undefined && !strictMode;
|
||||||
|
pattern += `/${hasOptional || isSlashOptional ? '?' : ''}`;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case TokenType.Static:
|
case TokenType.Static:
|
||||||
pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
|
pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
|
||||||
if (nextToken && nextToken.type === TokenType.Pattern) {
|
if (nextToken && nextToken.type === TokenType.Pattern) {
|
||||||
|
@ -112,32 +114,31 @@ export function createPathParser<P = unknown>(pathname: string, option: ParserOp
|
||||||
}
|
}
|
||||||
scores.push(MatchScore.static);
|
scores.push(MatchScore.static);
|
||||||
break;
|
break;
|
||||||
case TokenType.Param:
|
case TokenType.Param: {
|
||||||
{
|
// 动态参数支持形如/:param、/:param*、/:param?、/:param(\\d+)的形式
|
||||||
// 动态参数支持形如/:param、/:param*、/:param?、/:param(\\d+)的形式
|
let paramRegexp = '';
|
||||||
let paramRegexp = '';
|
if (nextToken) {
|
||||||
if (nextToken) {
|
switch (nextToken.type) {
|
||||||
switch (nextToken.type) {
|
case TokenType.LBracket:
|
||||||
case TokenType.LBracket:
|
// 跳过当前Token和左括号
|
||||||
// 跳过当前Token和左括号
|
tokenIdx += 2;
|
||||||
tokenIdx += 2;
|
while (tokens[tokenIdx].type !== TokenType.RBracket) {
|
||||||
while (tokens[tokenIdx].type !== TokenType.RBracket) {
|
paramRegexp += tokens[tokenIdx].value;
|
||||||
paramRegexp += tokens[tokenIdx].value;
|
|
||||||
tokenIdx++;
|
|
||||||
}
|
|
||||||
paramRegexp = `(${paramRegexp})`;
|
|
||||||
break;
|
|
||||||
case TokenType.Pattern:
|
|
||||||
tokenIdx++;
|
tokenIdx++;
|
||||||
paramRegexp += `(${nextToken.value === '*' ? '.*' : BASE_PARAM_PATTERN})${nextToken.value}`;
|
}
|
||||||
break;
|
paramRegexp = `(${paramRegexp})`;
|
||||||
}
|
break;
|
||||||
|
case TokenType.Pattern:
|
||||||
|
tokenIdx++;
|
||||||
|
paramRegexp += `(${nextToken.value === '*' ? '.*' : BASE_PARAM_PATTERN})${nextToken.value}`;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
pattern += paramRegexp ? `(?:${paramRegexp})` : `(${BASE_PARAM_PATTERN})`;
|
|
||||||
keys.push(token.value);
|
|
||||||
scores.push(MatchScore.param);
|
|
||||||
}
|
}
|
||||||
|
pattern += paramRegexp ? `(?:${paramRegexp})` : `(${BASE_PARAM_PATTERN})`;
|
||||||
|
keys.push(token.value);
|
||||||
|
scores.push(MatchScore.param);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case TokenType.WildCard:
|
case TokenType.WildCard:
|
||||||
keys.push(token.value);
|
keys.push(token.value);
|
||||||
pattern += `((?:${BASE_PARAM_PATTERN})${onlyHasWildCard ? '?' : ''}(?:/(?:${BASE_PARAM_PATTERN}))*)`;
|
pattern += `((?:${BASE_PARAM_PATTERN})${onlyHasWildCard ? '?' : ''}(?:/(?:${BASE_PARAM_PATTERN}))*)`;
|
||||||
|
@ -215,16 +216,15 @@ export function createPathParser<P = unknown>(pathname: string, option: ParserOp
|
||||||
}
|
}
|
||||||
path += params[token.value];
|
path += params[token.value];
|
||||||
break;
|
break;
|
||||||
case TokenType.WildCard:
|
case TokenType.WildCard: {
|
||||||
{
|
const wildCard = params['*'];
|
||||||
const wildCard = params['*'];
|
if (wildCard instanceof Array) {
|
||||||
if (wildCard instanceof Array) {
|
path += wildCard.join('/');
|
||||||
path += wildCard.join('/');
|
} else {
|
||||||
} else {
|
path += wildCard;
|
||||||
path += wildCard;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case TokenType.Delimiter:
|
case TokenType.Delimiter:
|
||||||
path += token.value;
|
path += token.value;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -95,4 +95,16 @@ describe('Dom Attribute', () => {
|
||||||
Inula.render(<div {...emptyStringProps} />, container);
|
Inula.render(<div {...emptyStringProps} />, container);
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('dangerouslySetInnerHTML和children同时设置,只渲染children', () => {
|
||||||
|
Inula.act(() => {
|
||||||
|
Inula.render(
|
||||||
|
<div className="root" dangerouslySetInnerHTML={{ __html: '1234' }}>
|
||||||
|
123
|
||||||
|
</div>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
});
|
||||||
|
expect(container.innerHTML).toBe('<div class="root">123</div>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,6 +17,25 @@ import { getPropDetails, PROPERTY_TYPE, PropDetails } from './PropertiesData';
|
||||||
|
|
||||||
const INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/;
|
const INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/;
|
||||||
|
|
||||||
|
const voidTagElements = [
|
||||||
|
'area',
|
||||||
|
'base',
|
||||||
|
'br',
|
||||||
|
'col',
|
||||||
|
'embed',
|
||||||
|
'hr',
|
||||||
|
'img',
|
||||||
|
'input',
|
||||||
|
'keygen',
|
||||||
|
'link',
|
||||||
|
'meta',
|
||||||
|
'param',
|
||||||
|
'source',
|
||||||
|
'track',
|
||||||
|
'wbr',
|
||||||
|
'menuitem',
|
||||||
|
];
|
||||||
|
|
||||||
// 是内置元素
|
// 是内置元素
|
||||||
export function isNativeElement(tagName: string, props: Record<string, any>) {
|
export function isNativeElement(tagName: string, props: Record<string, any>) {
|
||||||
return !tagName.includes('-') && props.is === undefined;
|
return !tagName.includes('-') && props.is === undefined;
|
||||||
|
@ -108,6 +127,18 @@ export function validateProps(type, props) {
|
||||||
throw new Error('style should be a object.');
|
throw new Error('style should be a object.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对于没有children的元素,设置dangerouslySetInnerHTML不生效
|
||||||
|
if (voidTagElements.includes(type)) {
|
||||||
|
if (props.dangerouslySetInnerHTML != null) {
|
||||||
|
delete props.dangerouslySetInnerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dangerouslySetInnerHTML和children同时设置,只渲染children
|
||||||
|
if (props.dangerouslySetInnerHTML != null && props.children != null) {
|
||||||
|
delete props.dangerouslySetInnerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
// 校验属性
|
// 校验属性
|
||||||
const invalidProps = Object.keys(props).filter(key => !isValidProp(type, key, props[key]));
|
const invalidProps = Object.keys(props).filter(key => !isValidProp(type, key, props[key]));
|
||||||
|
|
|
@ -72,6 +72,8 @@ import {
|
||||||
import { syncUpdates as flushSync } from './renderer/TreeBuilder';
|
import { syncUpdates as flushSync } from './renderer/TreeBuilder';
|
||||||
import { toRaw } from './inulax/proxy/ProxyHandler';
|
import { toRaw } from './inulax/proxy/ProxyHandler';
|
||||||
|
|
||||||
|
const version = __VERSION__;
|
||||||
|
|
||||||
const Inula = {
|
const Inula = {
|
||||||
Children,
|
Children,
|
||||||
createRef,
|
createRef,
|
||||||
|
@ -122,9 +124,9 @@ const Inula = {
|
||||||
Profiler,
|
Profiler,
|
||||||
StrictMode,
|
StrictMode,
|
||||||
Suspense,
|
Suspense,
|
||||||
|
version,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const version = __VERSION__;
|
|
||||||
export {
|
export {
|
||||||
Children,
|
Children,
|
||||||
createRef,
|
createRef,
|
||||||
|
@ -178,6 +180,7 @@ export {
|
||||||
Profiler,
|
Profiler,
|
||||||
StrictMode,
|
StrictMode,
|
||||||
Suspense,
|
Suspense,
|
||||||
|
version,
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
|
@ -200,6 +200,10 @@ function getChildByIndex(vNode: VNode, idx: number) {
|
||||||
// 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点
|
// 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点
|
||||||
export function calcStartUpdateVNode(treeRoot: VNode) {
|
export function calcStartUpdateVNode(treeRoot: VNode) {
|
||||||
const toUpdateNodes = Array.from(treeRoot.toUpdateNodes!);
|
const toUpdateNodes = Array.from(treeRoot.toUpdateNodes!);
|
||||||
|
// 所有待更新元素的parent为null说明该node的父元素已经被卸载,应该从根节点发起更新
|
||||||
|
if (toUpdateNodes.every(node => node.parent === null)) {
|
||||||
|
return treeRoot;
|
||||||
|
}
|
||||||
|
|
||||||
if (toUpdateNodes.length === 0) {
|
if (toUpdateNodes.length === 0) {
|
||||||
return treeRoot;
|
return treeRoot;
|
||||||
|
|
|
@ -145,7 +145,12 @@ export function captureRender(processing: VNode): VNode | null {
|
||||||
processUpdates(processing, inst, nextProps);
|
processUpdates(processing, inst, nextProps);
|
||||||
|
|
||||||
// 如果 props, state, context 都没有变化且 isForceUpdate 为 false则不需要更新
|
// 如果 props, state, context 都没有变化且 isForceUpdate 为 false则不需要更新
|
||||||
shouldUpdate = oldProps !== processing.props || inst.state !== processing.state || processing.isForceUpdate;
|
shouldUpdate =
|
||||||
|
oldProps !== processing.props ||
|
||||||
|
inst.state !== processing.state ||
|
||||||
|
processing.isForceUpdate ||
|
||||||
|
// 响应式状态管理器中的值变化,需要更新
|
||||||
|
processing.isStoreChange;
|
||||||
|
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
// derivedStateFromProps会修改nextState,因此需要调用
|
// derivedStateFromProps会修改nextState,因此需要调用
|
||||||
|
@ -167,7 +172,7 @@ export function captureRender(processing: VNode): VNode | null {
|
||||||
}
|
}
|
||||||
// 如果捕获了 error,必须更新
|
// 如果捕获了 error,必须更新
|
||||||
const isCatchError = (processing.flags & DidCapture) === DidCapture;
|
const isCatchError = (processing.flags & DidCapture) === DidCapture;
|
||||||
shouldUpdate = isCatchError || shouldUpdate || processing.isStoreChange;
|
shouldUpdate = isCatchError || shouldUpdate;
|
||||||
|
|
||||||
// 更新ref
|
// 更新ref
|
||||||
markRef(processing);
|
markRef(processing);
|
||||||
|
|
Loading…
Reference in New Issue