!166 inula 代码同步

Merge pull request !166 from xuan/sync
This commit is contained in:
涂旭辉 2024-04-02 03:21:55 +00:00 committed by Gitee
commit 8a7623d281
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
9 changed files with 111 additions and 41 deletions

View File

@ -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);
}; };
} }

View File

@ -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) {

View File

@ -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,

View File

@ -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;

View File

@ -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>');
});
}); });

View File

@ -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]));

View File

@ -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';

View File

@ -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;

View File

@ -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);