反応リストなし.マップ


私たちが反応してデータをレンダリングするとき、我々はしばしば配列をつかみ、Aを行います.map() インターフェイスを書くには.JSXマークアップのインストラクションステートメントを含めると、あまり理解できなくなります.
私が使用するコンポーネントを示します、そして、我々がそれを調べるように、我々は同時にJSX要素を操作する方法を学びます.

問題
この壊れたコードを取ると、リストを変更するときに頭を再現するバグだけでなく、複雑です.

function App1() {
    const [render, setRender] = useState(items)
    return (
        <Box>
            <List className="App">
                {/* WRITE THE LIST TO THE UI */}
                {render.map((item, index) => {
                    const [on, setOn] = useState(item.on)
                    return (
                        <ListItem key={index + item.name}>
                            <ListItemText primary={item.name} />
                            <ListItemSecondaryAction>
                                <Box display="flex">
                                    <Box>
                                        <Switch
                                            checked={on}
                                            onChange={() => setOn((on) => !on)}
                                        />
                                    </Box>
                                    <Box ml={1}>
                                        <IconButton
                                            color="secondary"
                                            onClick={() => remove(item)}
                                        >
                                            <MdClear />
                                        </IconButton>
                                    </Box>
                                </Box>
                            </ListItemSecondaryAction>
                        </ListItem>
                    )
                })}
            </List>
            <Button variant="contained" color="primary" onClick={add}>
                Add
            </Button>
        </Box>
    )

    function add() {
        setRender((items) => [
            { name: "Made up at " + Date.now(), on: false },
            ...items
        ])
    }

    function remove(item) {
        setRender((items) => items.filter((i) => i !== item))
    }
}
我々はアイテムのリストを持っているし、それらをレンダリングし、それぞれを操作したい.これは、最初の時間をレンダリングしますが、追加または削除アイコンをクリックし、それがクラッシュします.我々はマップ内のコンポーネントを使用していないので、フックを使用することはできません.試してみましょう
私はこのような多くの醜いコードを見ます.
いずれにしても、私たちの例題を作るためには、まずレンダリングするアイテムを抽出します.これによって、コードが動作しないようになります.

function RenderItem({ item, remove }) {
    const [on, setOn] = useState(item.on)
    return (
        <ListItem>
            <ListItemText primary={item.name} />
            <ListItemSecondaryAction>
                <Box display="flex">
                    <Box>
                        <Switch
                            checked={on}
                            onChange={() => setOn((on) => !on)}
                        />
                    </Box>
                    <Box ml={1}>
                        <IconButton
                            color="secondary"
                            onClick={() => remove(item)}
                        >
                            <MdClear />
                        </IconButton>
                    </Box>
                </Box>
            </ListItemSecondaryAction>
        </ListItem>
    )
}
一度我々はそれを使用するように我々のアプリを更新している:
function App2() {
    const [render, setRender] = useState(items)
    return (
        <Box>
            <List className="App">
                {render.map((item, index) => (
                    <RenderItem
                        remove={remove}
                        key={item.name + index}
                        item={item}
                    />
                ))}
            </List>
            <Button variant="contained" color="primary" onClick={add}>
                Add
            </Button>
        </Box>
    )

    function add() {
        setRender((items) => [
            { name: "Made up at " + Date.now(), on: false },
            ...items
        ])
    }

    function remove(item) {
        setRender((items) => items.filter((i) => i !== item))
    }
}

これは非常に良いですが、それはまだ混乱のビットは、我々の鍵の構造は、我々はアイテムが追加されたり削除されたときに我々は必要がない再レンダリングを作成するつもりです、そして我々はまだ{render.map など
次のように書きます.
function App4() {
    const [render, setRender] = useState(items)
    return (
        <Box>
            <List className="App">
                <Repeat list={render}>
                    <RenderItem remove={remove} />
                </Repeat>
            </List>
            <Button variant="contained" color="primary" onClick={add}>
                Add
            </Button>
        </Box>
    )

    function add() {
        setRender((items) => [
            { name: "Made up at " + Date.now(), on: false },
            ...items
        ])
    }

    function remove(item) {
        setRender((items) => items.filter((i) => i !== item))
    }
}
これは、リスト内の各項目に対してRenderItemが繰り返される必要があります.

解決策
ので、書きましょうRepeat 好きなことをするコンポーネント.
最初に知っていることは、我々が書くときconst something = <RenderItem remove={remove}/> 以下のようなオブジェクトを返します.{type: RenderItem, props: {remove: remove}} . この情報を使用して、この項目を次のような追加の小道具でレンダリングできます.

    const template = <RenderItem remove={remove}/>
    return <template.type {...template.props} something="else"/>

繰り返しコンポーネントを作るために使いましょう.
function Repeat({
    list,
    children,
    item = children.type ? children : undefined,
}) {
    if(!item) return
    return list.map((iterated, index) => {
        return (
            <item.type
                {...{ ...item.props, item: iterated, index }}
            />
        )
    })
}
我々は、レンダリングするもののためのアイテム支柱を使用して、繰り返しコンポーネントの子供たちにそれをデフォルトします.その後、このリストを実行します.リスト内の各項目についてindexitem によって渡されるパラメータに基づいて.map()これは良いですが、おそらく私たちが指定しない場合はchildren or item . 私たちは、単純なコンポーネントを作ることによってそれをすることができて、それよりむしろ後ろの背中としてそれを使うことができますundefined .
function Simple({ item }) {
    return <div>{typeof item === "object" ? JSON.stringify(item) : item}</div>
}
この関数には問題があります.そのため、まず、WeakMap リスト項目の一意キーを作成するには

const keys = new WeakMap()
let repeatId = 0
function getKey(item) {
    if (typeof item === "object") {
        const key = keys.get(item) ?? repeatId++
        keys.set(item, key)
        return key
    } else {
        return item
    }
}
この関数は、それが遭遇した項目の各オブジェクトタイプの一意の数値キーを作成します.現在の項目からキーを抽出するためのキー機能を取るか、デフォルトとしてこの一般的なものを使用するために、我々は繰り返し機能を強化することができます:
function Repeat({
    list,
    children,
    item = children.type ? children : <Simple />,
    keyFn = getKey
}) {
    return list.map((iterated, index) => {
        return (
            <item.type
                key={keyFn(iterated)}
                {...{ ...item.props, item: iterated, index }}
            />
        )
    })
}
たぶん、最終的なステップは、「アイテム」から離れていくつかの他の支柱を内部の構成要素のために使われるのを許すことです.それはかなり簡単です.
function Repeat({
    list,
    children,
    item = children.type ? children : <Simple />,
    pass = "item", // Take the name for the prop
    keyFn = getKey
}) {
    return list.map((iterated, index) => {
        return (
            <item.type
                key={keyFn(iterated)}
                // Use the passed in name
                {...{ ...item.props, [pass]: iterated, index }}
            />
        )
    })
}
最後の結果は完全に機能し、多くのバージョンよりも理由を使用して簡単に.map() - 少なくとも私の心に――)
記事からのすべてのコードは、ここにあります.
-

追加
コメントで作られたポイントのカップルに答えて、私は私がちょうど最適化すると思いましたRepeat より少ないメモリと割り当てを使用するには.map() バージョン.私も削除.map() リストが超ロングで、ガベージコレクションがかなり強力であるならば、アプリケーションロジックにより多くの変更が必要であるので、私はそれが必要でないと思います.
function Repeat({
    list,
    children,
    item = children.type ? children : <Simple />,
    pass = "item",
    keyFn = getKey
}) {
    const [keys] = useState({})
    const [output] = useState([])
    let index = 0
    for (let iterated of list) {
        let key = keyFn(iterated) ?? index
        output[index] = keys[key] = keys[key] || {
            ...item,
            key,
            props: { ...item.props, [pass]: iterated }
        }
        output[index].props.index = index
        index++
    }
    output.length = index
    return output
}
このバージョンについての1つの不満は、それがコンポーネントがマウントされている間、もはや見られないリスト項目のための構造を保持するということであるかもしれません.それらを取り除くことは可能かもしれませんが、overkillのようです、そして、あなたがallocationについて心配しているならば、それはトレードオフです.自然.map() 任意の場合、すべての場合は、配列やサブアイテムを作成している-今ので問題がある場合は、このバージョンはそれを避けるためのパターンです.