reactjs 在React中为多个文本子对象实现Read More功能

clj7thdc  于 5个月前  发布在  React
关注(0)|答案(2)|浏览(70)

我看到过多个npm包使用单个文本子组件来实现这一点,但它们似乎都不支持多个文本子组件。
例如,我想对一个有2个<p />子节点的组件实现Read More/Read Less:

function ReadMoreComponent (props) {
  return (
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
  )
}

字符串
如果Paragraph 1足够长,ReadMoreComponent将部分呈现Paragraph 1并显示Read More按钮。如果Paragraph 1短于限制,ReadMoreComponent将在显示Read More按钮之前显示整个Paragraph 1Paragraph 2的一部分。
是否可以在有多个文本子组件的React组件上实现Read More/Read Less功能?

q9yhzks0

q9yhzks01#

你可以尝试这样做。添加一些函数,可以使你的子组件的简短版本:

function short(arr, maxLen){
    let l=0;
    for (let i=0; i<arr.length; i++){
        if (l+arr[i].props.children.length > maxLen) {
              let res = arr.slice(0,i);
                let partial = arr[i].props.children.substring(0,maxLen-l)
                res.push((<p>{partial}</p>))
                return res;
            }
        l = l+arr[i].props.children.length;
    }
    return arr;
}

字符串
创建你的React组件,并使用它的状态来切换短/长模式:

function ReadMoreComponent (props) {
    const [shortMode, setShortMode] = useState(true);
  return (
    <React.Fragment>
      {shortMode ? (<React.Fragment>{short(props.children, 25)}</React.Fragment>) : props.children}
      <button onClick={() => setShortMode(!shortMode)}>{shortMode ? 'full' : 'short'}</button>
    </React.Fragment>
  )
}


使用您的组件:

<ReadMoreComponent>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
    <p>Paragraph 3</p>
    <p>Paragraph 4</p>
</ReadMoreComponent>

xytpbqjk

xytpbqjk2#

在您的情况下,您需要一个接受HTML元素(作为react元素)而不仅仅是字符串的组件,然后根据您指定的限制截断要显示的最终文本。
您可以解析输入,访问元素的子元素,直到到达字符串,然后计算字符串中的字符数,在达到最大字符数后截断并停止该过程。
下面是一个执行此操作的组件示例:

import React from 'react';

type ParseResult = {
    content: React.ReactNode | React.ReactNode[];
    remaining: number;
};

type Parse = (root: ParseResult, parse: Parse) => ParseResult;

const parseString = (text: string, remaining: number): ParseResult => {
    if (!text?.length) {
        return { content: text, remaining };
    }

    const newText =
        remaining <= 0
            ? ''
            : text.length > remaining
            ? text.substring(0, remaining)
            : text;

    return { content: newText, remaining: remaining - text.length };
};

const parse: Parse = (root, parseCallback) => {
    const { content, remaining = 0 } = root;

    if (typeof content === 'string') {
        const result = parseString(content, remaining);
        return result;
    } else if (React.isValidElement(content)) {
        const children = (content.props as { children?: React.ReactNode })
            ?.children;

        if (children != null) {
            const innerResult = parseCallback(
                {
                    content: children,
                    remaining,
                },
                parseCallback,
            );
            const newContent = React.cloneElement(content, {}, innerResult.content);
            return { content: newContent, remaining: innerResult.remaining };
        }
    } else if (Array.isArray(content)) {
        const list: React.ReactNode[] = [];
        let newRemaining = remaining;

        for (const child of content) {
            const parsed = parseCallback(
                { content: child, remaining: newRemaining },
                parseCallback,
            );
            newRemaining = parsed.remaining;
            const newContent = (
                <React.Fragment key={list.length}>{parsed.content}</React.Fragment>
            );
            list.push(newContent);

            if (newRemaining < 0) {
                break;
            }
        }

        return { content: list, remaining: newRemaining };
    }

    return root;
};

export interface ReadMoreAdditionalProps {
    expanded?: boolean;
    showMore?: React.ReactNode;
    showLess?: React.ReactNode;
}

const ReadMoreInner: React.FC<
    {
        children: React.ReactNode;
        truncate: number;
    } & ReadMoreAdditionalProps
> = ({ truncate, expanded, showMore, showLess, children }) => {
    const root: ParseResult = {
        content: children,
        remaining: truncate,
    };
    const result = parse(root, parse);

    return (
        <>
            {expanded ? children : result.content}
            {result.remaining < 0 ? (expanded ? showLess : showMore) : undefined}
        </>
    );
};

export interface ReadMoreOptions extends ReadMoreAdditionalProps {
    truncate?: number;
}

export type ReadMoreProps = ReadMoreOptions & {
    children: React.ReactNode;
};

export const ReadMore: React.FC<ReadMoreProps> = ({
    truncate,
    children,
    ...props
}) => {
    return truncate != null ? (
        <ReadMoreInner {...props} truncate={truncate}>
            {children}
        </ReadMoreInner>
    ) : (
        <>{children}</>
    );
};

字符串
请记住,该组件要求子组件包含最终文本(或不包含文本)。它不会访问其他属性(如texttitle属性),但这对于HTML组件应该是没问题的。
上面的组件本身不受HTML的约束,并且除了react之外不需要其他库,因此它也可以在react native中使用。
您可以在代码中包含此组件,添加按钮以显示更多或更少的文本,然后根据需要设置按钮的样式。
我创建了一个库来完成这个任务,如果您不想在项目中添加所有组件代码,我还创建了一个易于自定义的Web组件。
使用以下各项安装库:

npm install react-shorten


下面是如何使用该库的示例:

import { ReadMoreWeb } from 'react-shorten';
import React from 'react';

const MyComponent: React.FC<{
    truncate?: number;
    children: React.ReactNode;
}> = ({ truncate, children }) => (
    <ReadMoreWeb
        truncate={20}
        showMoreText="Show more"
        showLessText="Show less"
        className="read-more-btn"
    >
        <p>Paragraph 1</p>
        <p>Paragraph 2</p>
        <p>Paragraph 3</p>
        <p>Paragraph 4</p>
        <p>Paragraph 5</p>
        <p>Paragraph 6</p>
        <p>Paragraph 7</p>
    </ReadMoreWeb>
);


使用CSS:

.read-more-btn {
    display: inline;
    padding: 0;
    margin: 0;
    font-size: 1rem;
    color: rgb(80, 154, 223);
    background: none;
    border: none;
    cursor: pointer;
}

.link {
    color: rgb(26, 210, 243);
}


您可以看到here网络演示和here本地演示。“
如果不想安装其他库,也可以将这些演示用作参考,并将ReadMoreReadMoreWeb组件直接包含在项目中。

相关问题